
Python 3.13 has changed the behaviour of the `xml.dom.minidom` module:
double quotes used to be escaped everywhere, but they are now only
escaped inside attributes.
This was done by the following commit.
154477be72
As a result, the unit tests fail when executed on Python 3.13.
Fix this issue in the following way:
1. In all 88 reference-output XML files, replace all occurrences of
`"` with `"`.
2. Change `tests/conftest.py` to force-replace all occurrences of
`"` with `"` in the test output before comparing it to the
reference output. This ensures that the subsequent equality assertion
still holds on older versions of Python.
This solution works because the test data does not contain any instances
of `"` inside XML attributes.
Change-Id: Ied875014b1bb5d8225943303858658648efc12ad
244 lines
7.4 KiB
Python
244 lines
7.4 KiB
Python
import configparser
|
|
import pkg_resources
|
|
import xml.etree.ElementTree as XML
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from jenkins.plugins import Plugin
|
|
from jenkins_jobs.alphanum import AlphanumSort
|
|
from jenkins_jobs.config import JJBConfig
|
|
from jenkins_jobs.loader import Loader
|
|
from jenkins_jobs.modules import project_externaljob
|
|
from jenkins_jobs.modules import project_flow
|
|
from jenkins_jobs.modules import project_githuborg
|
|
from jenkins_jobs.modules import project_matrix
|
|
from jenkins_jobs.modules import project_maven
|
|
from jenkins_jobs.modules import project_multibranch
|
|
from jenkins_jobs.modules import project_multijob
|
|
from jenkins_jobs.registry import ModuleRegistry
|
|
from jenkins_jobs.xml_config import XmlJob, XmlJobGenerator, XmlViewGenerator
|
|
from jenkins_jobs import utils
|
|
|
|
from jenkins_jobs.roots import Roots
|
|
from jenkins_jobs.loader import load_files
|
|
|
|
|
|
# Avoid writing to ~/.cache/jenkins_jobs.
|
|
@pytest.fixture(autouse=True)
|
|
def job_cache_mocked(mocker):
|
|
mocker.patch("jenkins_jobs.builder.JobCache", autospec=True)
|
|
|
|
|
|
@pytest.fixture
|
|
def config_path(scenario):
|
|
return scenario.config_path
|
|
|
|
|
|
@pytest.fixture
|
|
def jjb_config(config_path):
|
|
config = JJBConfig(config_path)
|
|
config.validate()
|
|
return config
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_iter_entry_points():
|
|
config = configparser.ConfigParser()
|
|
config.read(Path(__file__).parent / "../setup.cfg")
|
|
groups = {}
|
|
for key in config["entry_points"]:
|
|
groups[key] = list()
|
|
for line in config["entry_points"][key].split("\n"):
|
|
if "" == line.strip():
|
|
continue
|
|
groups[key].append(
|
|
pkg_resources.EntryPoint.parse(line, dist=pkg_resources.Distribution())
|
|
)
|
|
|
|
def iter_entry_points(group, name=None):
|
|
return (entry for entry in groups[group] if name is None or name == entry.name)
|
|
|
|
return iter_entry_points
|
|
|
|
|
|
@pytest.fixture
|
|
def input(scenario, jjb_config):
|
|
loader = Loader.empty(jjb_config)
|
|
return loader.load_path(scenario.in_path)
|
|
|
|
|
|
@pytest.fixture
|
|
def plugins_info(scenario):
|
|
if not scenario.plugins_info_path.exists():
|
|
return None
|
|
plugin_dict_list = yaml.safe_load(scenario.plugins_info_path.read_text())
|
|
return [Plugin(**plugin_dict) for plugin_dict in plugin_dict_list]
|
|
|
|
|
|
@pytest.fixture
|
|
def registry(mocker, mock_iter_entry_points, jjb_config, plugins_info):
|
|
mocker.patch("pkg_resources.iter_entry_points", side_effect=mock_iter_entry_points)
|
|
return ModuleRegistry(jjb_config, plugins_info)
|
|
|
|
|
|
@pytest.fixture
|
|
def project(input, registry):
|
|
type_to_class = {
|
|
"maven": project_maven.Maven,
|
|
"matrix": project_matrix.Matrix,
|
|
"flow": project_flow.Flow,
|
|
"githuborg": project_githuborg.GithubOrganization,
|
|
"multijob": project_multijob.MultiJob,
|
|
"multibranch": project_multibranch.WorkflowMultiBranch,
|
|
"multibranch-defaults": project_multibranch.WorkflowMultiBranchDefaults,
|
|
"externaljob": project_externaljob.ExternalJob,
|
|
}
|
|
try:
|
|
class_name = input["project-type"]
|
|
except KeyError:
|
|
return None
|
|
if class_name == "freestyle":
|
|
return None
|
|
cls = type_to_class[class_name]
|
|
return cls(registry)
|
|
|
|
|
|
@pytest.fixture
|
|
def expected_output(scenario):
|
|
if not scenario.out_paths:
|
|
# Do not check output if there are no files for it.
|
|
return None
|
|
return "".join(path.read_text() for path in sorted(scenario.out_paths))
|
|
|
|
|
|
@pytest.fixture
|
|
def expected_error(scenario):
|
|
if scenario.error_path.exists():
|
|
return scenario.error_path.read_text().rstrip()
|
|
else:
|
|
return None
|
|
|
|
|
|
# Tests use output files directories as expected folder name.
|
|
def check_folders(scenario, job_xml_list):
|
|
root_dir = scenario.in_path.parent
|
|
|
|
def name_parent(name):
|
|
*dirs, name = name.split("/")
|
|
return "/".join(dirs)
|
|
|
|
def path_parent(path):
|
|
if scenario.in_path.is_dir():
|
|
# In directory tests, output file directory does not
|
|
# indicate expected job folder.
|
|
base_dir = scenario.in_path
|
|
else:
|
|
base_dir = root_dir
|
|
dir = str(path.relative_to(base_dir).parent)
|
|
if dir == ".":
|
|
return ""
|
|
else:
|
|
return dir
|
|
|
|
actual_dirs = list(sorted(set(name_parent(jx.name) for jx in job_xml_list)))
|
|
expected_dirs = list(sorted(path_parent(path) for path in scenario.out_paths))
|
|
assert expected_dirs == actual_dirs
|
|
|
|
|
|
@pytest.fixture
|
|
def check_generator(scenario, input, expected_output, jjb_config, registry, project):
|
|
def check(Generator):
|
|
if project:
|
|
xml = project.root_xml(input)
|
|
else:
|
|
xml = XML.Element("project")
|
|
|
|
generator = Generator(registry)
|
|
generator.gen_xml(xml, input)
|
|
pretty_xml = (
|
|
XmlJob(xml, "fixturejob")
|
|
.output()
|
|
.decode()
|
|
.replace(""", '"') # Ensure compatibility with Python < 3.13
|
|
)
|
|
assert expected_output == pretty_xml
|
|
|
|
return check
|
|
|
|
|
|
@pytest.fixture
|
|
def check_parser(jjb_config, registry):
|
|
def check(in_path):
|
|
roots = Roots(jjb_config)
|
|
load_files(jjb_config, roots, [in_path])
|
|
registry.set_macros(roots.macros)
|
|
job_data_list = roots.generate_jobs()
|
|
view_data_list = roots.generate_views()
|
|
generator = XmlJobGenerator(registry)
|
|
_ = generator.generateXML(job_data_list)
|
|
_ = generator.generateXML(view_data_list)
|
|
|
|
return check
|
|
|
|
|
|
@pytest.fixture
|
|
def check_job(scenario, expected_output, jjb_config, registry):
|
|
def check():
|
|
roots = Roots(jjb_config)
|
|
if jjb_config.recursive:
|
|
path_list = [Path(p) for p in utils.recurse_path(str(scenario.in_path))]
|
|
else:
|
|
path_list = [scenario.in_path]
|
|
load_files(jjb_config, roots, path_list)
|
|
registry.set_macros(roots.macros)
|
|
job_data_list = roots.generate_jobs()
|
|
registry.amend_job_dicts(job_data_list)
|
|
generator = XmlJobGenerator(registry)
|
|
job_xml_list = generator.generateXML(job_data_list)
|
|
job_xml_list.sort(key=AlphanumSort)
|
|
|
|
pretty_xml = (
|
|
"\n".join(job.output().decode() for job in job_xml_list)
|
|
.strip()
|
|
.replace("\n\n", "\n")
|
|
.replace(""", '"') # Ensure compatibility with Python < 3.13
|
|
)
|
|
if expected_output is None:
|
|
return
|
|
stripped_expected_output = (
|
|
expected_output.strip().replace("<BLANKLINE>", "").replace("\n\n", "\n")
|
|
)
|
|
assert stripped_expected_output == pretty_xml
|
|
|
|
check_folders(scenario, job_xml_list)
|
|
|
|
return check
|
|
|
|
|
|
@pytest.fixture
|
|
def check_view(scenario, expected_output, jjb_config, registry):
|
|
def check():
|
|
roots = Roots(jjb_config)
|
|
load_files(jjb_config, roots, [scenario.in_path])
|
|
registry.set_macros(roots.macros)
|
|
view_data_list = roots.generate_views()
|
|
generator = XmlViewGenerator(registry)
|
|
view_xml_list = generator.generateXML(view_data_list)
|
|
view_xml_list.sort(key=AlphanumSort)
|
|
|
|
pretty_xml = (
|
|
"\n".join(view.output().decode() for view in view_xml_list)
|
|
.strip()
|
|
.replace("\n\n", "\n")
|
|
)
|
|
if expected_output is None:
|
|
return
|
|
stripped_expected_output = (
|
|
expected_output.strip().replace("<BLANKLINE>", "").replace("\n\n", "\n")
|
|
)
|
|
assert stripped_expected_output == pretty_xml
|
|
|
|
return check
|