Author: svn-role
Date: Fri Jul 10 04:00:31 2026
New Revision: 1936034

Log:
Merge the 1.15.x-r1935776 branch:

 * r1935776
   Rework XML output validation in tests.
   Justification:
     Support testing with embeddable Python that doesn't provide 'venv'.
   Branch:
     1.15.x-r1935776
   Votes:
     +1: ivan, kotkov, brane

Modified:
   subversion/branches/1.15.x/   (props changed)
   subversion/branches/1.15.x/.github/workflows/autoconf.yml
   subversion/branches/1.15.x/.github/workflows/cmake.yml
   subversion/branches/1.15.x/CMakeLists.txt
   subversion/branches/1.15.x/INSTALL
   subversion/branches/1.15.x/Makefile.in
   subversion/branches/1.15.x/STATUS
   subversion/branches/1.15.x/build/run_tests.py
   subversion/branches/1.15.x/subversion/libsvn_subr/utf8proc/   (props changed)
   subversion/branches/1.15.x/subversion/tests/cmdline/prop_tests.py
   subversion/branches/1.15.x/subversion/tests/cmdline/svntest/main.py
   subversion/branches/1.15.x/subversion/tests/cmdline/svntest/verify.py
   subversion/branches/1.15.x/win-tests.py

Modified: subversion/branches/1.15.x/.github/workflows/autoconf.yml
==============================================================================
--- subversion/branches/1.15.x/.github/workflows/autoconf.yml   Fri Jul 10 
02:38:56 2026        (r1936033)
+++ subversion/branches/1.15.x/.github/workflows/autoconf.yml   Fri Jul 10 
04:00:31 2026        (r1936034)
@@ -114,7 +114,9 @@ jobs:
         run: make -j
 
       - name: Run tests
-        run: make ${{matrix.check-target}} PARALLEL=16 APACHE_MPM=event
+        run: |
+          make ${{matrix.check-target}} PARALLEL=16 APACHE_MPM=event \
+              CHECK_XML_SCHEMA=1
 
       - name: Archive test logs
         if: always()

Modified: subversion/branches/1.15.x/.github/workflows/cmake.yml
==============================================================================
--- subversion/branches/1.15.x/.github/workflows/cmake.yml      Fri Jul 10 
02:38:56 2026        (r1936033)
+++ subversion/branches/1.15.x/.github/workflows/cmake.yml      Fri Jul 10 
04:00:31 2026        (r1936034)
@@ -219,6 +219,7 @@ jobs:
           -DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/installdir
           -DVCPKG_TARGET_TRIPLET=${{ matrix.vcpkg_triplet }}
           -DSVN_TEST_CONFIGURE_FOR_PARALLEL=ON
+          -DSVN_TEST_CHECK_XML_SCHEMA=ON
           -DPython3_ROOT_DIR="${{ steps.platform.outputs.python_root }}"
           -DPython3_FIND_REGISTRY=NEVER
           -DPython3_FIND_STRATEGY=LOCATION

Modified: subversion/branches/1.15.x/CMakeLists.txt
==============================================================================
--- subversion/branches/1.15.x/CMakeLists.txt   Fri Jul 10 02:38:56 2026        
(r1936033)
+++ subversion/branches/1.15.x/CMakeLists.txt   Fri Jul 10 04:00:31 2026        
(r1936034)
@@ -84,6 +84,7 @@ option(SVN_ENABLE_TESTS "Build Subversio
 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_TEST_CHECK_XML_SCHEMA "Enable extended XML schema validation" OFF)
 option(SVN_ENABLE_APACHE_MODULES "Build modules for Apache HTTPD" OFF)
 
 option(SVN_ENABLE_SWIG_PERL "Enable Subversion SWIG bindings for Perl" OFF)
@@ -819,21 +820,6 @@ if(SVN_ENABLE_TESTS)
   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 "${test_base_dir}/${name}")
@@ -847,12 +833,13 @@ if(SVN_ENABLE_TESTS)
       NAME
         "${name}"
       COMMAND
-        "${python3_test_executable}" "${run_tests_script}"
+        "${Python3_EXECUTABLE}" "${run_tests_script}"
         --bin ${binary_dir}
         --tools-bin ${binary_dir}
         --verbose
         --log-to-stdout
         --set-log-level=WARNING
+        $<$<BOOL:${SVN_TEST_CHECK_XML_SCHEMA}>:--check-xml-schema>
         ${SVN_TEST_COMMAND_ARGUMENTS}
         ${CMAKE_CURRENT_SOURCE_DIR}
         ${test_root}

Modified: subversion/branches/1.15.x/INSTALL
==============================================================================
--- subversion/branches/1.15.x/INSTALL  Fri Jul 10 02:38:56 2026        
(r1936033)
+++ subversion/branches/1.15.x/INSTALL  Fri Jul 10 04:00:31 2026        
(r1936034)
@@ -21,6 +21,7 @@ Contents:
        D. Installing from a Zip or Installer File under Windows
        E. Building the Latest Source under Windows
        F. Building using CMake
+       G. Running the test suite
 
    III. BUILDING A SUBVERSION SERVER
        A. Setting Up Apache Httpd
@@ -693,10 +694,8 @@ II.   INSTALLATION
           $ make
           # make install
 
-      You can also run the full test suite by running 'make check'.  Even
-      in successful runs, some tests will report XFAIL; that is normal.
-      Failed runs are indicated by FAIL or XPASS results, or a non-zero exit
-      code from "make check".
+      You can also run the full test suite by running 'make check'.  See the
+      section G for more information.
 
 
   B.  Building the Latest Source under Unix
@@ -1256,6 +1255,64 @@ II.   INSTALLATION
 
             <VCPKG_ROOT>/scripts/buildsystems/vcpkg.cmake
 
+
+  G.  Running the test suite
+      ----------------------
+
+      The test suite can be run using any of the build systems above.
+
+      Each test may report PASS, XFAIL, FAIL or XPASS.
+
+      The first two statuses are expected and "normal":
+      - PASS means a test completed with successful result.
+      - XFAIL means a test failed, but this is a known issue.
+
+      The last two statuses are unexpected and indicate a failure:
+      - FAIL means a test returned an unexpected result.
+      - XPASS means a test which was expected to fail completed successfully.
+
+
+  G.1 Validating XML output
+
+      Some Subversion commands can format their output as XML.  The test suite
+      will always verify that the output is valid XML, however it is also
+      possible to check the output against the expected XML schema. To do
+      this, install the lxml and rnc2rng Python modules and start the tests
+      using the --check-xml-schema argument.
+
+
+  G.2 Running the test suite under the autoconf/make build system
+
+      Tests can be started with either of the following commands
+      - make check [options]
+        Run the test suite using a local repository ("file://...")
+      - make davautocheck [options]
+        Run the test suite using a http:// repository on the local machine
+      - make svnserveautocheck [options]
+        Run the test suite using a svnserve:// repository on the local machine
+      - make check-javahl [options]
+        Test the javahl bindings
+
+      For either command, the usual -j argument can be used to have make run
+      several tests concurrently.
+
+      The following list are options commonly used:
+      - PARALLEL=n
+        Run n python based command line tests concurrently
+      - TESTS=[path to python based test in subversion/tests/cmdline/]
+        Run a specific Python based test
+      - APACHE_MPM=mpm type
+        Used for davautocheck, run the test using the specified Apache httpd
+        MPM engine
+      - CHECK_XML_SCHEMA
+        If this option is defined, full XML schema validation will be
+        performed, see the --check-xml-schema option above.
+
+
+  G.3 Running the test suite under the CMake build system
+
+      Run the ctests command from the build directory.
+
 
 III.  BUILDING A SUBVERSION SERVER
       ============================

Modified: subversion/branches/1.15.x/Makefile.in
==============================================================================
--- subversion/branches/1.15.x/Makefile.in      Fri Jul 10 02:38:56 2026        
(r1936033)
+++ subversion/branches/1.15.x/Makefile.in      Fri Jul 10 04:00:31 2026        
(r1936034)
@@ -643,6 +643,9 @@ check: bin @TRANSFORM_LIBTOOL_SCRIPTS@ $
          if test "$(VALGRIND_OPTS)" != ""; then                             \
            flags="--valgrind-opts $(VALGRIND_OPTS) $$flags";                \
          fi;                                                                \
+         if test "$(CHECK_XML_SCHEMA)" != ""; then             \
+           flags="--check-xml-schema $$flags";                 \
+         fi;                                                                \
          LD_LIBRARY_PATH='$(auth_plugin_dirs):$(LD_LIBRARY_PATH)'           \
          $(PYTHON) $(top_srcdir)/build/run_tests.py                         \
                    --config-file $(top_srcdir)/subversion/tests/tests.conf  \

Modified: subversion/branches/1.15.x/STATUS
==============================================================================
--- subversion/branches/1.15.x/STATUS   Fri Jul 10 02:38:56 2026        
(r1936033)
+++ subversion/branches/1.15.x/STATUS   Fri Jul 10 04:00:31 2026        
(r1936034)
@@ -57,12 +57,3 @@ Veto-blocked changes:
 
 Approved changes:
 =================
-
- * r1935776
-   Rework XML output validation in tests.
-   Justification:
-     Support testing with embeddable Python that doesn't provide 'venv'.
-   Branch:
-     1.15.x-r1935776
-   Votes:
-     +1: ivan, kotkov, brane

Modified: subversion/branches/1.15.x/build/run_tests.py
==============================================================================
--- subversion/branches/1.15.x/build/run_tests.py       Fri Jul 10 02:38:56 
2026        (r1936033)
+++ subversion/branches/1.15.x/build/run_tests.py       Fri Jul 10 04:00:31 
2026        (r1936034)
@@ -259,8 +259,6 @@ 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:
@@ -317,6 +315,8 @@ class TestHarness:
       cmdline.append('--valgrind=%s' % self.opts.valgrind)
     if self.opts.valgrind_opts is not None:
       cmdline.append('--valgrind-opts=%s' % self.opts.valgrind_opts)
+    if self.opts.check_xml_schema is not None:
+      cmdline.append('--check-xml-schema')
 
     self.py_test_cmdline = cmdline
 
@@ -330,15 +330,6 @@ class TestHarness:
       svntest = importlib.import_module('svntest')
       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.
-      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)
 
@@ -1041,13 +1032,6 @@ 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',
@@ -1107,19 +1091,14 @@ def create_parser():
                     help='programs to run under valgrind')
   parser.add_option('--valgrind-opts', action='store',
                     help='options to pass valgrind')
+  parser.add_option('--check-xml-schema', action='store_true',
+                    help='Enable extended XML schema validation')
 
   parser.set_defaults(set_log_level=None)
   return 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(
@@ -1142,25 +1121,6 @@ def main():
   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/1.15.x/subversion/tests/cmdline/prop_tests.py
==============================================================================
--- subversion/branches/1.15.x/subversion/tests/cmdline/prop_tests.py   Fri Jul 
10 02:38:56 2026        (r1936033)
+++ subversion/branches/1.15.x/subversion/tests/cmdline/prop_tests.py   Fri Jul 
10 04:00:31 2026        (r1936034)
@@ -2641,8 +2641,7 @@ def xml_unsafe_author(sbox):
 
 @Issue(4415)
 @Issue(4919)
-@XFail(lambda: (svntest.main.is_bad_xml_fatal()
-                and not svntest.main.is_ra_type_dav()))
+@XFail(lambda: (not svntest.main.is_ra_type_dav()))
 def xml_unsafe_author2(sbox):
   "svn:author with XML unsafe chars 2"
 

Modified: subversion/branches/1.15.x/subversion/tests/cmdline/svntest/main.py
==============================================================================
--- subversion/branches/1.15.x/subversion/tests/cmdline/svntest/main.py Fri Jul 
10 02:38:56 2026        (r1936033)
+++ subversion/branches/1.15.x/subversion/tests/cmdline/svntest/main.py Fri Jul 
10 04:00:31 2026        (r1936034)
@@ -36,11 +36,9 @@ import xml
 import urllib
 import logging
 import hashlib
-import importlib
 import zipfile
 import codecs
 import queue
-import venv
 
 from urllib.parse import quote as urllib_parse_quote
 from urllib.parse import unquote as urllib_parse_unquote
@@ -121,13 +119,11 @@ class SVNRepositoryCreateFailure(Failure
 if sys.platform == 'win32':
   windows = True
   file_scheme_prefix = 'file:///'
-  venv_bin = 'Scripts'
   _exe = '.exe'
   _bat = '.bat'
   os.environ['SVN_DBG_STACKTRACES_TO_STDERR'] = 'y'
 else:
   windows = False
-  venv_bin = 'bin'
   file_scheme_prefix = 'file://'
   _exe = ''
   _bat = ''
@@ -221,16 +217,6 @@ options = None
 # this dir, so there's one point at which to mount, e.g., a ramdisk.
 work_dir = "svn-test-work"
 
-# Directory for the Python virtual environment where we install
-# external dependencies of the test environment
-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"]
-
 # Constant for the merge info property.
 SVN_PROP_MERGEINFO = "svn:mergeinfo"
 
@@ -1756,11 +1742,8 @@ 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 is_xml_schema_validation_enabled():
+  return options.check_xml_schema
 
 
 def wc_format(ver=None):
@@ -1866,14 +1849,14 @@ 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:
       args.append('--valgrind=' + options.valgrind)
     if options.valgrind_opts:
       args.append('--valgrind-opts=' + options.valgrind_opts)
+    if options.check_xml_schema:
+      args.append('--check-xml-schema')
 
     result, stdout_lines, stderr_lines = spawn_process(command, 0, False, None,
                                                        *args)
@@ -2242,9 +2225,6 @@ 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',
@@ -2321,6 +2301,8 @@ def _create_parser(usage=None):
                     help='programs to run under valgrind')
   parser.add_option('--valgrind-opts', action='store',
                     help='options to pass to valgrind')
+  parser.add_option('--check-xml-schema', action='store_true',
+                    help='Enable extended XML schema validation')
 
   # most of the defaults are None, but some are other values, set them here
   parser.set_defaults(
@@ -2351,8 +2333,6 @@ 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)
@@ -2402,10 +2382,6 @@ def parse_options(arglist=sys.argv[1:],
                     svn_wc__max_supported_format_version(),
                     options.wc_format_version))
 
-  if options.venv_base:
-    venv_base = options.venv_base
-    venv_create = False
-
   return (parser, args)
 
 def tweak_options_for_precooked_repos():
@@ -2438,77 +2414,6 @@ def run_tests(test_list, serial_only = F
 
   sys.exit(execute_tests(test_list, serial_only))
 
-def _get_purelib_dir(venv_dir):
-  if sys.platform == 'win32':
-    return os.path.join(venv_dir, "Lib", "site-packages")
-  else:
-    return os.path.join(venv_dir, "lib", "python%d.%d" % sys.version_info[:2],
-                        "site-packages")
-
-def ensure_dependencies():
-  """Install the dependencies we need for running the tests.
-
-  NOTE: this function des not handle the case where the Python
-        version has changed. In theory, we could automagically
-        upgrade the venv in that case. In practice, we won't.
-  """
-
-  venv_dir = os.path.abspath(venv_path())
-  package_path = _get_purelib_dir(venv_dir)
-
-  # 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):
-      if os.path.exists(venv_dir):
-        safe_rmtree(venv_dir)
-      venv.create(venv_dir, with_pip=True)
-
-    # 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, stdout=subprocess.PIPE if quiet else None)
-    importlib.invalidate_caches()
-
-    python_prog = os.path.join(venv_dir, venv_bin, "python" + _exe)
-    python_path = _get_purelib_dir(venv_dir)
-    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
      tracker and determine what the target milestone is and
@@ -2707,10 +2612,6 @@ 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/1.15.x/subversion/tests/cmdline/svntest/verify.py
==============================================================================
--- subversion/branches/1.15.x/subversion/tests/cmdline/svntest/verify.py       
Fri Jul 10 02:38:56 2026        (r1936033)
+++ subversion/branches/1.15.x/subversion/tests/cmdline/svntest/verify.py       
Fri Jul 10 04:00:31 2026        (r1936034)
@@ -32,6 +32,8 @@ import itertools
 from io import BytesIO
 from typing import Iterable
 
+import xml.etree.ElementTree
+
 import svntest
 
 logger = logging.getLogger()
@@ -1053,23 +1055,30 @@ __schema_dir = os.path.join(
           os.path.abspath(__file__))))),
   "svn", "schema")
 def validate_xml_schema(name: str, lines: Iterable[str]) -> None:
-  schema_name = name + ".rnc"
+  source = ''.join(lines)
+
+  if svntest.main.is_xml_schema_validation_enabled():
+    # Use full XML schema validation (requires lxml and rnc2rng packages)
+    schema_name = name + ".rnc"
+    try:
+      from lxml import etree #type:ignore
+      schema_file = os.path.join(__schema_dir, schema_name)
+      schema = etree.RelaxNG(file=schema_file)
+      document = etree.parse(BytesIO(source.encode("utf-8")))
+      if not schema.validate(document):
+        raise SVNXMLSchemaValidationError(schema.error_log)
+    except ImportError:
+      logger.error("XML: Module lxml.etree not found")
+      raise svntest.Failure
+    except Exception as ex:
+      logger.error("XML: " + str(ex))
+      logger.warning("XML:\n" + "\n".join(repr(line) for line in lines))
+      raise
+
+  # Always parse XML using built in XML parser to check structural validity.
   try:
-    # Imported in this scope because sys.path may not have been updated yet.
-    from lxml import etree #type:ignore
-    schema_file = os.path.join(__schema_dir, schema_name)
-    schema = etree.RelaxNG(file=schema_file)
-    source = ''.join(lines)
-    document = etree.parse(BytesIO(source.encode("utf-8")))
-    if not schema.validate(document):
-      raise SVNXMLSchemaValidationError(schema.error_log)
-  except ImportError:
-    logger.error("XML: Module lxml.etree not found")
-    return
+    xml.etree.ElementTree.fromstring(source)
   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)
+    raise

Modified: subversion/branches/1.15.x/win-tests.py
==============================================================================
--- subversion/branches/1.15.x/win-tests.py     Fri Jul 10 02:38:56 2026        
(r1936033)
+++ subversion/branches/1.15.x/win-tests.py     Fri Jul 10 04:00:31 2026        
(r1936034)
@@ -116,6 +116,7 @@ def _usage_exit():
   print("  --fsfs-compression=VAL : Set compression type to VAL (for fsfs)")
   print("  --wc-format-version=VAL: Set the WC format version")
   print("  --store-pristine=VAL   : Set the WC pristine mode")
+  print("  --check-xml-schema     : Enable extended XML schema validation")
   print("  -q, --quiet            : Deprecated; this is the default.")
   print("                           Use --set-log-level instead.")
 
@@ -148,7 +149,7 @@ opts, args = my_getopt(sys.argv[1:], 'hr
                         'ssl-cert=', 'exclusive-wc-locks', 'memcached-server=',
                         'skip-c-tests', 'dump-load-cross-check', 
'memcached-dir=',
                         'fsfs-compression=', 'wc-format-version=',
-                        'store-pristine='
+                        'store-pristine=', 'check-xml-schema'
                         ])
 if len(args) > 1:
   print('Warning: non-option arguments after the first one will be ignored')
@@ -198,6 +199,7 @@ fsfs_compression = None
 fsfs_dir_deltification = None
 wc_format_version = None
 store_pristine = None
+check_xml_schema = None
 
 for opt, val in opts:
   if opt in ('-h', '--help'):
@@ -300,6 +302,8 @@ for opt, val in opts:
     wc_format_version = val
   elif opt == '--store-pristine':
     store_pristine = val
+  elif opt == '--check-xml-schema':
+    check_xml_schema = True
 
 # Calculate the source and test directory names
 abs_srcdir = os.path.abspath("")
@@ -1143,6 +1147,7 @@ if not test_javahl and not test_swig:
   opts.fsfs_dir_deltification = fsfs_dir_deltification
   opts.wc_format_version = wc_format_version
   opts.store_pristine = store_pristine
+  opts.check_xml_schema = check_xml_schema
   th = run_tests.TestHarness(abs_srcdir, abs_builddir,
                              log_file, fail_log_file, opts)
   old_cwd = os.getcwd()

Reply via email to