2019-08-01 03:32:58 +00:00
|
|
|
# Copyright (C) 2019 The Android Open Source Project
|
|
|
|
#
|
|
|
|
# Licensed 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.
|
|
|
|
|
|
|
|
"""Unittests for the manifest_xml.py module."""
|
|
|
|
|
2020-02-20 03:36:26 +00:00
|
|
|
import os
|
2021-03-09 23:19:06 +00:00
|
|
|
import platform
|
2021-04-12 19:16:36 +00:00
|
|
|
import re
|
2020-09-06 18:53:18 +00:00
|
|
|
import tempfile
|
2019-08-01 03:32:58 +00:00
|
|
|
import unittest
|
2020-02-22 10:30:12 +00:00
|
|
|
import xml.dom.minidom
|
2019-08-01 03:32:58 +00:00
|
|
|
|
|
|
|
import error
|
|
|
|
import manifest_xml
|
|
|
|
|
|
|
|
|
2021-02-26 02:53:49 +00:00
|
|
|
# Invalid paths that we don't want in the filesystem.
|
|
|
|
INVALID_FS_PATHS = (
|
2023-03-11 06:46:20 +00:00
|
|
|
"",
|
|
|
|
".",
|
|
|
|
"..",
|
|
|
|
"../",
|
|
|
|
"./",
|
|
|
|
".//",
|
|
|
|
"foo/",
|
|
|
|
"./foo",
|
|
|
|
"../foo",
|
|
|
|
"foo/./bar",
|
|
|
|
"foo/../../bar",
|
|
|
|
"/foo",
|
|
|
|
"./../foo",
|
|
|
|
".git/foo",
|
2021-02-26 02:53:49 +00:00
|
|
|
# Check case folding.
|
2023-03-11 06:46:20 +00:00
|
|
|
".GIT/foo",
|
|
|
|
"blah/.git/foo",
|
|
|
|
".repo/foo",
|
|
|
|
".repoconfig",
|
2021-02-26 02:53:49 +00:00
|
|
|
# Block ~ due to 8.3 filenames on Windows filesystems.
|
2023-03-11 06:46:20 +00:00
|
|
|
"~",
|
|
|
|
"foo~",
|
|
|
|
"blah/foo~",
|
2021-02-26 02:53:49 +00:00
|
|
|
# Block Unicode characters that get normalized out by filesystems.
|
2023-03-11 06:46:20 +00:00
|
|
|
"foo\u200Cbar",
|
2021-04-30 03:15:31 +00:00
|
|
|
# Block newlines.
|
2023-03-11 06:46:20 +00:00
|
|
|
"f\n/bar",
|
|
|
|
"f\r/bar",
|
2021-02-26 02:53:49 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
# Make sure platforms that use path separators (e.g. Windows) are also
|
|
|
|
# rejected properly.
|
2023-03-11 06:46:20 +00:00
|
|
|
if os.path.sep != "/":
|
|
|
|
INVALID_FS_PATHS += tuple(
|
|
|
|
x.replace("/", os.path.sep) for x in INVALID_FS_PATHS
|
|
|
|
)
|
2021-02-26 02:53:49 +00:00
|
|
|
|
|
|
|
|
2021-04-12 19:16:36 +00:00
|
|
|
def sort_attributes(manifest):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Sort the attributes of all elements alphabetically.
|
|
|
|
|
|
|
|
This is needed because different versions of the toxml() function from
|
|
|
|
xml.dom.minidom outputs the attributes of elements in different orders.
|
|
|
|
Before Python 3.8 they were output alphabetically, later versions preserve
|
|
|
|
the order specified by the user.
|
|
|
|
|
|
|
|
Args:
|
|
|
|
manifest: String containing an XML manifest.
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
The XML manifest with the attributes of all elements sorted
|
|
|
|
alphabetically.
|
|
|
|
"""
|
|
|
|
new_manifest = ""
|
|
|
|
# This will find every element in the XML manifest, whether they have
|
|
|
|
# attributes or not. This simplifies recreating the manifest below.
|
|
|
|
matches = re.findall(
|
|
|
|
r'(<[/?]?[a-z-]+\s*)((?:\S+?="[^"]+"\s*?)*)(\s*[/?]?>)', manifest
|
|
|
|
)
|
|
|
|
for head, attrs, tail in matches:
|
|
|
|
m = re.findall(r'\S+?="[^"]+"', attrs)
|
|
|
|
new_manifest += head + " ".join(sorted(m)) + tail
|
|
|
|
return new_manifest
|
2021-04-12 19:16:36 +00:00
|
|
|
|
|
|
|
|
2021-02-25 09:54:56 +00:00
|
|
|
class ManifestParseTestCase(unittest.TestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""TestCase for parsing manifests."""
|
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.tempdirobj = tempfile.TemporaryDirectory(prefix="repo_tests")
|
|
|
|
self.tempdir = self.tempdirobj.name
|
|
|
|
self.repodir = os.path.join(self.tempdir, ".repo")
|
|
|
|
self.manifest_dir = os.path.join(self.repodir, "manifests")
|
|
|
|
self.manifest_file = os.path.join(
|
|
|
|
self.repodir, manifest_xml.MANIFEST_FILE_NAME
|
|
|
|
)
|
|
|
|
self.local_manifest_dir = os.path.join(
|
|
|
|
self.repodir, manifest_xml.LOCAL_MANIFESTS_DIR_NAME
|
|
|
|
)
|
|
|
|
os.mkdir(self.repodir)
|
|
|
|
os.mkdir(self.manifest_dir)
|
|
|
|
|
|
|
|
# The manifest parsing really wants a git repo currently.
|
|
|
|
gitdir = os.path.join(self.repodir, "manifests.git")
|
|
|
|
os.mkdir(gitdir)
|
|
|
|
with open(os.path.join(gitdir, "config"), "w") as fp:
|
|
|
|
fp.write(
|
|
|
|
"""[remote "origin"]
|
2021-02-25 09:54:56 +00:00
|
|
|
url = https://localhost:0/manifest
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
2021-02-25 09:54:56 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def tearDown(self):
|
|
|
|
self.tempdirobj.cleanup()
|
2021-02-25 09:54:56 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def getXmlManifest(self, data):
|
|
|
|
"""Helper to initialize a manifest for testing."""
|
|
|
|
with open(self.manifest_file, "w", encoding="utf-8") as fp:
|
|
|
|
fp.write(data)
|
|
|
|
return manifest_xml.XmlManifest(self.repodir, self.manifest_file)
|
2021-02-25 09:54:56 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
@staticmethod
|
|
|
|
def encodeXmlAttr(attr):
|
|
|
|
"""Encode |attr| using XML escape rules."""
|
|
|
|
return attr.replace("\r", "
").replace("\n", "
")
|
2021-04-30 03:15:31 +00:00
|
|
|
|
2021-02-25 09:54:56 +00:00
|
|
|
|
2019-08-01 03:32:58 +00:00
|
|
|
class ManifestValidateFilePaths(unittest.TestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Check _ValidateFilePaths helper.
|
|
|
|
|
|
|
|
This doesn't access a real filesystem.
|
|
|
|
"""
|
|
|
|
|
|
|
|
def check_both(self, *args):
|
|
|
|
manifest_xml.XmlManifest._ValidateFilePaths("copyfile", *args)
|
|
|
|
manifest_xml.XmlManifest._ValidateFilePaths("linkfile", *args)
|
|
|
|
|
|
|
|
def test_normal_path(self):
|
|
|
|
"""Make sure good paths are accepted."""
|
|
|
|
self.check_both("foo", "bar")
|
|
|
|
self.check_both("foo/bar", "bar")
|
|
|
|
self.check_both("foo", "bar/bar")
|
|
|
|
self.check_both("foo/bar", "bar/bar")
|
|
|
|
|
|
|
|
def test_symlink_targets(self):
|
|
|
|
"""Some extra checks for symlinks."""
|
|
|
|
|
|
|
|
def check(*args):
|
|
|
|
manifest_xml.XmlManifest._ValidateFilePaths("linkfile", *args)
|
|
|
|
|
|
|
|
# We allow symlinks to end in a slash since we allow them to point to
|
|
|
|
# dirs in general. Technically the slash isn't necessary.
|
|
|
|
check("foo/", "bar")
|
|
|
|
# We allow a single '.' to get a reference to the project itself.
|
|
|
|
check(".", "bar")
|
|
|
|
|
|
|
|
def test_bad_paths(self):
|
|
|
|
"""Make sure bad paths (src & dest) are rejected."""
|
|
|
|
for path in INVALID_FS_PATHS:
|
|
|
|
self.assertRaises(
|
|
|
|
error.ManifestInvalidPathError, self.check_both, path, "a"
|
|
|
|
)
|
|
|
|
self.assertRaises(
|
|
|
|
error.ManifestInvalidPathError, self.check_both, "a", path
|
|
|
|
)
|
2020-02-22 10:30:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ValueTests(unittest.TestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Check utility parsing code."""
|
|
|
|
|
|
|
|
def _get_node(self, text):
|
|
|
|
return xml.dom.minidom.parseString(text).firstChild
|
|
|
|
|
|
|
|
def test_bool_default(self):
|
|
|
|
"""Check XmlBool default handling."""
|
|
|
|
node = self._get_node("<node/>")
|
|
|
|
self.assertIsNone(manifest_xml.XmlBool(node, "a"))
|
|
|
|
self.assertIsNone(manifest_xml.XmlBool(node, "a", None))
|
|
|
|
self.assertEqual(123, manifest_xml.XmlBool(node, "a", 123))
|
|
|
|
|
|
|
|
node = self._get_node('<node a=""/>')
|
|
|
|
self.assertIsNone(manifest_xml.XmlBool(node, "a"))
|
|
|
|
|
|
|
|
def test_bool_invalid(self):
|
|
|
|
"""Check XmlBool invalid handling."""
|
|
|
|
node = self._get_node('<node a="moo"/>')
|
|
|
|
self.assertEqual(123, manifest_xml.XmlBool(node, "a", 123))
|
|
|
|
|
|
|
|
def test_bool_true(self):
|
|
|
|
"""Check XmlBool true values."""
|
|
|
|
for value in ("yes", "true", "1"):
|
2023-09-29 15:04:49 +00:00
|
|
|
node = self._get_node(f'<node a="{value}"/>')
|
2023-03-11 06:46:20 +00:00
|
|
|
self.assertTrue(manifest_xml.XmlBool(node, "a"))
|
|
|
|
|
|
|
|
def test_bool_false(self):
|
|
|
|
"""Check XmlBool false values."""
|
|
|
|
for value in ("no", "false", "0"):
|
2023-09-29 15:04:49 +00:00
|
|
|
node = self._get_node(f'<node a="{value}"/>')
|
2023-03-11 06:46:20 +00:00
|
|
|
self.assertFalse(manifest_xml.XmlBool(node, "a"))
|
|
|
|
|
|
|
|
def test_int_default(self):
|
|
|
|
"""Check XmlInt default handling."""
|
|
|
|
node = self._get_node("<node/>")
|
|
|
|
self.assertIsNone(manifest_xml.XmlInt(node, "a"))
|
|
|
|
self.assertIsNone(manifest_xml.XmlInt(node, "a", None))
|
|
|
|
self.assertEqual(123, manifest_xml.XmlInt(node, "a", 123))
|
|
|
|
|
|
|
|
node = self._get_node('<node a=""/>')
|
|
|
|
self.assertIsNone(manifest_xml.XmlInt(node, "a"))
|
|
|
|
|
|
|
|
def test_int_good(self):
|
|
|
|
"""Check XmlInt numeric handling."""
|
|
|
|
for value in (-1, 0, 1, 50000):
|
2023-09-29 15:04:49 +00:00
|
|
|
node = self._get_node(f'<node a="{value}"/>')
|
2023-03-11 06:46:20 +00:00
|
|
|
self.assertEqual(value, manifest_xml.XmlInt(node, "a"))
|
|
|
|
|
|
|
|
def test_int_invalid(self):
|
|
|
|
"""Check XmlInt invalid handling."""
|
|
|
|
with self.assertRaises(error.ManifestParseError):
|
|
|
|
node = self._get_node('<node a="xx"/>')
|
|
|
|
manifest_xml.XmlInt(node, "a")
|
2020-09-06 18:53:18 +00:00
|
|
|
|
|
|
|
|
2021-02-25 09:54:56 +00:00
|
|
|
class XmlManifestTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Check manifest processing."""
|
|
|
|
|
|
|
|
def test_empty(self):
|
|
|
|
"""Parse an 'empty' manifest file."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
'<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.remotes, {})
|
|
|
|
self.assertEqual(manifest.projects, [])
|
|
|
|
|
|
|
|
def test_link(self):
|
|
|
|
"""Verify Link handling with new names."""
|
|
|
|
manifest = manifest_xml.XmlManifest(self.repodir, self.manifest_file)
|
|
|
|
with open(os.path.join(self.manifest_dir, "foo.xml"), "w") as fp:
|
|
|
|
fp.write("<manifest></manifest>")
|
|
|
|
manifest.Link("foo.xml")
|
|
|
|
with open(self.manifest_file) as fp:
|
|
|
|
self.assertIn('<include name="foo.xml" />', fp.read())
|
|
|
|
|
|
|
|
def test_toxml_empty(self):
|
|
|
|
"""Verify the ToXml() helper."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
'<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.ToXml().toxml(), '<?xml version="1.0" ?><manifest/>'
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_todict_empty(self):
|
|
|
|
"""Verify the ToDict() helper."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
'<?xml version="1.0" encoding="UTF-8"?>' "<manifest></manifest>"
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.ToDict(), {})
|
|
|
|
|
|
|
|
def test_toxml_omit_local(self):
|
|
|
|
"""Does not include local_manifests projects when omit_local=True."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
'<?xml version="1.0" encoding="UTF-8"?><manifest>'
|
|
|
|
'<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
|
|
|
|
'<project name="p" groups="local::me"/>'
|
|
|
|
'<project name="q"/>'
|
|
|
|
'<project name="r" groups="keep"/>'
|
|
|
|
"</manifest>"
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml(omit_local=True).toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch=".." name="a"/><default remote="a" revision="r"/>'
|
|
|
|
'<project name="q"/><project groups="keep" name="r"/></manifest>',
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_toxml_with_local(self):
|
|
|
|
"""Does include local_manifests projects when omit_local=False."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
'<?xml version="1.0" encoding="UTF-8"?><manifest>'
|
|
|
|
'<remote name="a" fetch=".."/><default remote="a" revision="r"/>'
|
|
|
|
'<project name="p" groups="local::me"/>'
|
|
|
|
'<project name="q"/>'
|
|
|
|
'<project name="r" groups="keep"/>'
|
|
|
|
"</manifest>"
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml(omit_local=False).toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch=".." name="a"/><default remote="a" revision="r"/>'
|
|
|
|
'<project groups="local::me" name="p"/>'
|
|
|
|
'<project name="q"/><project groups="keep" name="r"/></manifest>',
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_repo_hooks(self):
|
|
|
|
"""Check repo-hooks settings."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2020-12-04 10:32:06 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<project name="repohooks" path="src/repohooks"/>
|
|
|
|
<repo-hooks in-project="repohooks" enabled-list="a, b"/>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.repo_hooks_project.name, "repohooks")
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.repo_hooks_project.enabled_repo_hooks, ["a", "b"]
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_repo_hooks_unordered(self):
|
|
|
|
"""Check repo-hooks settings work even if the project def comes second.""" # noqa: E501
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-09-21 22:23:55 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<repo-hooks in-project="repohooks" enabled-list="a, b"/>
|
|
|
|
<project name="repohooks" path="src/repohooks"/>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.repo_hooks_project.name, "repohooks")
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.repo_hooks_project.enabled_repo_hooks, ["a", "b"]
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_unknown_tags(self):
|
|
|
|
"""Check superproject settings."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-01-11 20:18:47 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<superproject name="superproject"/>
|
|
|
|
<iankaz value="unknown (possible) future tags are ignored"/>
|
|
|
|
<x-custom-tag>X tags are always ignored</x-custom-tag>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.name, "superproject")
|
|
|
|
self.assertEqual(manifest.superproject.remote.name, "test-remote")
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="test-remote"/>'
|
|
|
|
'<default remote="test-remote" revision="refs/heads/main"/>'
|
|
|
|
'<superproject name="superproject"/>'
|
|
|
|
"</manifest>",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_remote_annotations(self):
|
|
|
|
"""Check remote settings."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-07-20 20:52:33 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost">
|
|
|
|
<annotation name="foo" value="bar"/>
|
|
|
|
</remote>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.remotes["test-remote"].annotations[0].name, "foo"
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.remotes["test-remote"].annotations[0].value, "bar"
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="test-remote">'
|
|
|
|
'<annotation name="foo" value="bar"/>'
|
|
|
|
"</remote>"
|
|
|
|
"</manifest>",
|
|
|
|
)
|
2021-07-20 20:52:33 +00:00
|
|
|
|
2023-10-20 15:35:39 +00:00
|
|
|
def test_parse_with_xml_doctype(self):
|
|
|
|
"""Check correct manifest parse with DOCTYPE node present."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<!DOCTYPE manifest []>
|
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<project name="test-project" path="src/test-project"/>
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(len(manifest.projects), 1)
|
|
|
|
self.assertEqual(manifest.projects[0].name, "test-project")
|
|
|
|
|
2021-02-26 02:53:49 +00:00
|
|
|
|
|
|
|
class IncludeElementTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Tests for <include>."""
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-04-18 02:36:50 +00:00
|
|
|
def test_revision_default(self):
|
|
|
|
"""Check handling of revision attribute."""
|
|
|
|
root_m = os.path.join(self.manifest_dir, "root.xml")
|
|
|
|
with open(root_m, "w") as fp:
|
|
|
|
fp.write(
|
|
|
|
"""
|
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<include name="stable.xml" revision="stable-branch" />
|
|
|
|
<project name="root-name1" path="root-path1" />
|
|
|
|
<project name="root-name2" path="root-path2" />
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
with open(os.path.join(self.manifest_dir, "stable.xml"), "w") as fp:
|
|
|
|
fp.write(
|
|
|
|
"""
|
|
|
|
<manifest>
|
|
|
|
<project name="stable-name1" path="stable-path1" />
|
|
|
|
<project name="stable-name2" path="stable-path2" revision="stable-branch2" />
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
include_m = manifest_xml.XmlManifest(self.repodir, root_m)
|
|
|
|
for proj in include_m.projects:
|
|
|
|
if proj.name == "root-name1":
|
|
|
|
# Check include revision not set on root level proj.
|
|
|
|
self.assertNotEqual("stable-branch", proj.revisionExpr)
|
|
|
|
if proj.name == "root-name2":
|
|
|
|
# Check root proj revision not removed.
|
|
|
|
self.assertEqual("refs/heads/main", proj.revisionExpr)
|
|
|
|
if proj.name == "stable-name1":
|
|
|
|
# Check stable proj has inherited revision include node.
|
|
|
|
self.assertEqual("stable-branch", proj.revisionExpr)
|
|
|
|
if proj.name == "stable-name2":
|
|
|
|
# Check stable proj revision can override include node.
|
|
|
|
self.assertEqual("stable-branch2", proj.revisionExpr)
|
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_group_levels(self):
|
|
|
|
root_m = os.path.join(self.manifest_dir, "root.xml")
|
|
|
|
with open(root_m, "w") as fp:
|
|
|
|
fp.write(
|
|
|
|
"""
|
2021-02-26 02:53:49 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<include name="level1.xml" groups="level1-group" />
|
|
|
|
<project name="root-name1" path="root-path1" />
|
|
|
|
<project name="root-name2" path="root-path2" groups="r2g1,r2g2" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
with open(os.path.join(self.manifest_dir, "level1.xml"), "w") as fp:
|
|
|
|
fp.write(
|
|
|
|
"""
|
2021-02-26 02:53:49 +00:00
|
|
|
<manifest>
|
|
|
|
<include name="level2.xml" groups="level2-group" />
|
|
|
|
<project name="level1-name1" path="level1-path1" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
with open(os.path.join(self.manifest_dir, "level2.xml"), "w") as fp:
|
|
|
|
fp.write(
|
|
|
|
"""
|
2021-02-26 02:53:49 +00:00
|
|
|
<manifest>
|
|
|
|
<project name="level2-name1" path="level2-path1" groups="l2g1,l2g2" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
include_m = manifest_xml.XmlManifest(self.repodir, root_m)
|
|
|
|
for proj in include_m.projects:
|
|
|
|
if proj.name == "root-name1":
|
|
|
|
# Check include group not set on root level proj.
|
|
|
|
self.assertNotIn("level1-group", proj.groups)
|
|
|
|
if proj.name == "root-name2":
|
|
|
|
# Check root proj group not removed.
|
|
|
|
self.assertIn("r2g1", proj.groups)
|
|
|
|
if proj.name == "level1-name1":
|
|
|
|
# Check level1 proj has inherited group level 1.
|
|
|
|
self.assertIn("level1-group", proj.groups)
|
|
|
|
if proj.name == "level2-name1":
|
|
|
|
# Check level2 proj has inherited group levels 1 and 2.
|
|
|
|
self.assertIn("level1-group", proj.groups)
|
|
|
|
self.assertIn("level2-group", proj.groups)
|
|
|
|
# Check level2 proj group not removed.
|
|
|
|
self.assertIn("l2g1", proj.groups)
|
|
|
|
|
|
|
|
def test_allow_bad_name_from_user(self):
|
|
|
|
"""Check handling of bad name attribute from the user's input."""
|
|
|
|
|
|
|
|
def parse(name):
|
|
|
|
name = self.encodeXmlAttr(name)
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
f"""
|
2021-02-26 02:53:49 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<include name="{name}" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
# Force the manifest to be parsed.
|
|
|
|
manifest.ToXml()
|
|
|
|
|
|
|
|
# Setup target of the include.
|
|
|
|
target = os.path.join(self.tempdir, "target.xml")
|
|
|
|
with open(target, "w") as fp:
|
|
|
|
fp.write("<manifest></manifest>")
|
|
|
|
|
|
|
|
# Include with absolute path.
|
|
|
|
parse(os.path.abspath(target))
|
|
|
|
|
|
|
|
# Include with relative path.
|
|
|
|
parse(os.path.relpath(target, self.manifest_dir))
|
|
|
|
|
|
|
|
def test_bad_name_checks(self):
|
|
|
|
"""Check handling of bad name attribute."""
|
|
|
|
|
|
|
|
def parse(name):
|
|
|
|
name = self.encodeXmlAttr(name)
|
|
|
|
# Setup target of the include.
|
|
|
|
with open(
|
|
|
|
os.path.join(self.manifest_dir, "target.xml"),
|
|
|
|
"w",
|
|
|
|
encoding="utf-8",
|
|
|
|
) as fp:
|
|
|
|
fp.write(f'<manifest><include name="{name}"/></manifest>')
|
|
|
|
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-03-02 02:38:08 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<include name="target.xml" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
# Force the manifest to be parsed.
|
|
|
|
manifest.ToXml()
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
# Handle empty name explicitly because a different codepath rejects it.
|
|
|
|
with self.assertRaises(error.ManifestParseError):
|
|
|
|
parse("")
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
for path in INVALID_FS_PATHS:
|
|
|
|
if not path:
|
|
|
|
continue
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
with self.assertRaises(error.ManifestInvalidPathError):
|
|
|
|
parse(path)
|
2021-02-26 02:53:49 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ProjectElementTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Tests for <project>."""
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_group(self):
|
|
|
|
"""Check project group settings."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2020-09-06 18:53:18 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<project name="test-name" path="test-path"/>
|
|
|
|
<project name="extras" path="path" groups="g1,g2,g1"/>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(len(manifest.projects), 2)
|
|
|
|
# Ordering isn't guaranteed.
|
|
|
|
result = {
|
|
|
|
manifest.projects[0].name: manifest.projects[0].groups,
|
|
|
|
manifest.projects[1].name: manifest.projects[1].groups,
|
|
|
|
}
|
|
|
|
self.assertCountEqual(
|
|
|
|
result["test-name"], ["name:test-name", "all", "path:test-path"]
|
|
|
|
)
|
|
|
|
self.assertCountEqual(
|
|
|
|
result["extras"],
|
|
|
|
["g1", "g2", "g1", "name:extras", "all", "path:path"],
|
|
|
|
)
|
|
|
|
groupstr = "default,platform-" + platform.system().lower()
|
|
|
|
self.assertEqual(groupstr, manifest.GetGroupsStr())
|
|
|
|
groupstr = "g1,g2,g1"
|
|
|
|
manifest.manifestProject.config.SetString("manifest.groups", groupstr)
|
|
|
|
self.assertEqual(groupstr, manifest.GetGroupsStr())
|
|
|
|
|
|
|
|
def test_set_revision_id(self):
|
|
|
|
"""Check setting of project's revisionId."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-02-06 17:44:15 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="test-name"/>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(len(manifest.projects), 1)
|
|
|
|
project = manifest.projects[0]
|
|
|
|
project.SetRevisionId("ABCDEF")
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="default-remote"/>'
|
|
|
|
'<default remote="default-remote" revision="refs/heads/main"/>'
|
|
|
|
'<project name="test-name" revision="ABCDEF" upstream="refs/heads/main"/>' # noqa: E501
|
|
|
|
"</manifest>",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_trailing_slash(self):
|
|
|
|
"""Check handling of trailing slashes in attributes."""
|
|
|
|
|
|
|
|
def parse(name, path):
|
|
|
|
name = self.encodeXmlAttr(name)
|
|
|
|
path = self.encodeXmlAttr(path)
|
|
|
|
return self.getXmlManifest(
|
|
|
|
f"""
|
2020-10-06 10:55:14 +00:00
|
|
|
<manifest>
|
2021-02-26 02:53:49 +00:00
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="{name}" path="{path}" />
|
2020-10-06 10:55:14 +00:00
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
|
|
|
manifest = parse("a/path/", "foo")
|
|
|
|
self.assertEqual(
|
|
|
|
os.path.normpath(manifest.projects[0].gitdir),
|
|
|
|
os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
os.path.normpath(manifest.projects[0].objdir),
|
|
|
|
os.path.join(
|
|
|
|
self.tempdir, ".repo", "project-objects", "a", "path.git"
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
manifest = parse("a/path", "foo/")
|
|
|
|
self.assertEqual(
|
|
|
|
os.path.normpath(manifest.projects[0].gitdir),
|
|
|
|
os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
os.path.normpath(manifest.projects[0].objdir),
|
|
|
|
os.path.join(
|
|
|
|
self.tempdir, ".repo", "project-objects", "a", "path.git"
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
manifest = parse("a/path", "foo//////")
|
|
|
|
self.assertEqual(
|
|
|
|
os.path.normpath(manifest.projects[0].gitdir),
|
|
|
|
os.path.join(self.tempdir, ".repo", "projects", "foo.git"),
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
os.path.normpath(manifest.projects[0].objdir),
|
|
|
|
os.path.join(
|
|
|
|
self.tempdir, ".repo", "project-objects", "a", "path.git"
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_toplevel_path(self):
|
|
|
|
"""Check handling of path=. specially."""
|
|
|
|
|
|
|
|
def parse(name, path):
|
|
|
|
name = self.encodeXmlAttr(name)
|
|
|
|
path = self.encodeXmlAttr(path)
|
|
|
|
return self.getXmlManifest(
|
|
|
|
f"""
|
2021-03-11 04:35:44 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="{name}" path="{path}" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
|
|
|
|
for path in (".", "./", ".//", ".///"):
|
|
|
|
manifest = parse("server/path", path)
|
|
|
|
self.assertEqual(
|
|
|
|
os.path.normpath(manifest.projects[0].gitdir),
|
|
|
|
os.path.join(self.tempdir, ".repo", "projects", "..git"),
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_bad_path_name_checks(self):
|
|
|
|
"""Check handling of bad path & name attributes."""
|
|
|
|
|
|
|
|
def parse(name, path):
|
|
|
|
name = self.encodeXmlAttr(name)
|
|
|
|
path = self.encodeXmlAttr(path)
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
f"""
|
2020-10-06 10:55:14 +00:00
|
|
|
<manifest>
|
2021-02-26 02:53:49 +00:00
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="{name}" path="{path}" />
|
2020-10-06 10:55:14 +00:00
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
# Force the manifest to be parsed.
|
|
|
|
manifest.ToXml()
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
# Verify the parser is valid by default to avoid buggy tests below.
|
|
|
|
parse("ok", "ok")
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
# Handle empty name explicitly because a different codepath rejects it.
|
|
|
|
# Empty path is OK because it defaults to the name field.
|
|
|
|
with self.assertRaises(error.ManifestParseError):
|
|
|
|
parse("", "ok")
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
for path in INVALID_FS_PATHS:
|
|
|
|
if not path or path.endswith("/") or path.endswith(os.path.sep):
|
|
|
|
continue
|
2021-02-26 02:53:49 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
with self.assertRaises(error.ManifestInvalidPathError):
|
|
|
|
parse(path, "ok")
|
2021-03-11 04:35:44 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
# We have a dedicated test for path=".".
|
|
|
|
if path not in {"."}:
|
|
|
|
with self.assertRaises(error.ManifestInvalidPathError):
|
|
|
|
parse("ok", path)
|
2021-02-25 09:54:56 +00:00
|
|
|
|
|
|
|
|
2021-02-26 02:53:49 +00:00
|
|
|
class SuperProjectElementTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Tests for <superproject>."""
|
2021-02-25 09:54:56 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_superproject(self):
|
|
|
|
"""Check superproject settings."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-02-25 09:54:56 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<superproject name="superproject"/>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.name, "superproject")
|
|
|
|
self.assertEqual(manifest.superproject.remote.name, "test-remote")
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.superproject.remote.url, "http://localhost/superproject"
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.revision, "refs/heads/main")
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="test-remote"/>'
|
|
|
|
'<default remote="test-remote" revision="refs/heads/main"/>'
|
|
|
|
'<superproject name="superproject"/>'
|
|
|
|
"</manifest>",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_superproject_revision(self):
|
|
|
|
"""Check superproject settings with a different revision attribute"""
|
|
|
|
self.maxDiff = None
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-09-27 06:20:32 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/main" />
|
|
|
|
<superproject name="superproject" revision="refs/heads/stable" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.name, "superproject")
|
|
|
|
self.assertEqual(manifest.superproject.remote.name, "test-remote")
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.superproject.remote.url, "http://localhost/superproject"
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.revision, "refs/heads/stable")
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="test-remote"/>'
|
|
|
|
'<default remote="test-remote" revision="refs/heads/main"/>'
|
|
|
|
'<superproject name="superproject" revision="refs/heads/stable"/>'
|
|
|
|
"</manifest>",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_superproject_revision_default_negative(self):
|
|
|
|
"""Check superproject settings with a same revision attribute"""
|
|
|
|
self.maxDiff = None
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-09-27 06:20:32 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" />
|
|
|
|
<default remote="test-remote" revision="refs/heads/stable" />
|
|
|
|
<superproject name="superproject" revision="refs/heads/stable" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.name, "superproject")
|
|
|
|
self.assertEqual(manifest.superproject.remote.name, "test-remote")
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.superproject.remote.url, "http://localhost/superproject"
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.revision, "refs/heads/stable")
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="test-remote"/>'
|
|
|
|
'<default remote="test-remote" revision="refs/heads/stable"/>'
|
|
|
|
'<superproject name="superproject"/>'
|
|
|
|
"</manifest>",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_superproject_revision_remote(self):
|
|
|
|
"""Check superproject settings with a same revision attribute"""
|
|
|
|
self.maxDiff = None
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-09-27 06:20:32 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="test-remote" fetch="http://localhost" revision="refs/heads/main" />
|
|
|
|
<default remote="test-remote" />
|
|
|
|
<superproject name="superproject" revision="refs/heads/stable" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
""" # noqa: E501
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.name, "superproject")
|
|
|
|
self.assertEqual(manifest.superproject.remote.name, "test-remote")
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.superproject.remote.url, "http://localhost/superproject"
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.revision, "refs/heads/stable")
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="test-remote" revision="refs/heads/main"/>' # noqa: E501
|
|
|
|
'<default remote="test-remote"/>'
|
|
|
|
'<superproject name="superproject" revision="refs/heads/stable"/>'
|
|
|
|
"</manifest>",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_remote(self):
|
|
|
|
"""Check superproject settings with a remote."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-02-25 09:54:56 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<remote name="superproject-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<superproject name="platform/superproject" remote="superproject-remote"/>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.name, "platform/superproject")
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.superproject.remote.name, "superproject-remote"
|
|
|
|
)
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.superproject.remote.url,
|
|
|
|
"http://localhost/platform/superproject",
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.revision, "refs/heads/main")
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="default-remote"/>'
|
|
|
|
'<remote fetch="http://localhost" name="superproject-remote"/>'
|
|
|
|
'<default remote="default-remote" revision="refs/heads/main"/>'
|
|
|
|
'<superproject name="platform/superproject" remote="superproject-remote"/>' # noqa: E501
|
|
|
|
"</manifest>",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_defalut_remote(self):
|
|
|
|
"""Check superproject settings with a default remote."""
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-02-25 09:54:56 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<superproject name="superproject" remote="default-remote"/>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.superproject.name, "superproject")
|
|
|
|
self.assertEqual(manifest.superproject.remote.name, "default-remote")
|
|
|
|
self.assertEqual(manifest.superproject.revision, "refs/heads/main")
|
|
|
|
self.assertEqual(
|
|
|
|
sort_attributes(manifest.ToXml().toxml()),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
'<remote fetch="http://localhost" name="default-remote"/>'
|
|
|
|
'<default remote="default-remote" revision="refs/heads/main"/>'
|
|
|
|
'<superproject name="superproject"/>'
|
|
|
|
"</manifest>",
|
|
|
|
)
|
2021-05-04 19:32:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ContactinfoElementTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Tests for <contactinfo>."""
|
2021-05-04 19:32:13 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_contactinfo(self):
|
|
|
|
"""Check contactinfo settings."""
|
|
|
|
bugurl = "http://localhost/contactinfo"
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
f"""
|
2021-05-04 19:32:13 +00:00
|
|
|
<manifest>
|
|
|
|
<contactinfo bugurl="{bugurl}"/>
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.contactinfo.bugurl, bugurl)
|
|
|
|
self.assertEqual(
|
|
|
|
manifest.ToXml().toxml(),
|
|
|
|
'<?xml version="1.0" ?><manifest>'
|
|
|
|
f'<contactinfo bugurl="{bugurl}"/>'
|
|
|
|
"</manifest>",
|
|
|
|
)
|
2021-06-09 15:21:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
class DefaultElementTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Tests for <default>."""
|
|
|
|
|
|
|
|
def test_default(self):
|
|
|
|
"""Check default settings."""
|
|
|
|
a = manifest_xml._Default()
|
|
|
|
a.revisionExpr = "foo"
|
|
|
|
a.remote = manifest_xml._XmlRemote(name="remote")
|
|
|
|
b = manifest_xml._Default()
|
|
|
|
b.revisionExpr = "bar"
|
|
|
|
self.assertEqual(a, a)
|
|
|
|
self.assertNotEqual(a, b)
|
|
|
|
self.assertNotEqual(b, a.remote)
|
|
|
|
self.assertNotEqual(a, 123)
|
|
|
|
self.assertNotEqual(a, None)
|
2021-06-09 15:21:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RemoteElementTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Tests for <remote>."""
|
|
|
|
|
|
|
|
def test_remote(self):
|
|
|
|
"""Check remote settings."""
|
|
|
|
a = manifest_xml._XmlRemote(name="foo")
|
|
|
|
a.AddAnnotation("key1", "value1", "true")
|
|
|
|
b = manifest_xml._XmlRemote(name="foo")
|
|
|
|
b.AddAnnotation("key2", "value1", "true")
|
|
|
|
c = manifest_xml._XmlRemote(name="foo")
|
|
|
|
c.AddAnnotation("key1", "value2", "true")
|
|
|
|
d = manifest_xml._XmlRemote(name="foo")
|
|
|
|
d.AddAnnotation("key1", "value1", "false")
|
|
|
|
self.assertEqual(a, a)
|
|
|
|
self.assertNotEqual(a, b)
|
|
|
|
self.assertNotEqual(a, c)
|
|
|
|
self.assertNotEqual(a, d)
|
|
|
|
self.assertNotEqual(a, manifest_xml._Default())
|
|
|
|
self.assertNotEqual(a, 123)
|
|
|
|
self.assertNotEqual(a, None)
|
2021-06-30 08:58:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
class RemoveProjectElementTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Tests for <remove-project>."""
|
2021-06-30 08:58:28 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_remove_one_project(self):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-06-30 08:58:28 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="myproject" />
|
|
|
|
<remove-project name="myproject" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.projects, [])
|
2021-06-30 08:58:28 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_remove_one_project_one_remains(self):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-06-30 08:58:28 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="myproject" />
|
|
|
|
<project name="yourproject" />
|
|
|
|
<remove-project name="myproject" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
2021-06-30 08:58:28 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
self.assertEqual(len(manifest.projects), 1)
|
|
|
|
self.assertEqual(manifest.projects[0].name, "yourproject")
|
2021-06-30 08:58:28 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_remove_one_project_doesnt_exist(self):
|
|
|
|
with self.assertRaises(manifest_xml.ManifestParseError):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-06-30 08:58:28 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<remove-project name="myproject" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
manifest.projects
|
2021-06-30 08:58:28 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_remove_one_optional_project_doesnt_exist(self):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2021-06-30 08:58:28 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<remove-project name="myproject" optional="true" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(manifest.projects, [])
|
2020-06-13 09:10:40 +00:00
|
|
|
|
2023-05-31 14:56:34 +00:00
|
|
|
def test_remove_using_path_attrib(self):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="project1" path="tests/path1" />
|
|
|
|
<project name="project1" path="tests/path2" />
|
|
|
|
<project name="project2" />
|
|
|
|
<project name="project3" />
|
|
|
|
<project name="project4" path="tests/path3" />
|
|
|
|
<project name="project4" path="tests/path4" />
|
|
|
|
<project name="project5" />
|
|
|
|
<project name="project6" path="tests/path6" />
|
|
|
|
|
|
|
|
<remove-project name="project1" path="tests/path2" />
|
|
|
|
<remove-project name="project3" />
|
|
|
|
<remove-project name="project4" />
|
|
|
|
<remove-project path="project5" />
|
|
|
|
<remove-project path="tests/path6" />
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
found_proj1_path1 = False
|
|
|
|
found_proj2 = False
|
|
|
|
for proj in manifest.projects:
|
|
|
|
if proj.name == "project1":
|
|
|
|
found_proj1_path1 = True
|
|
|
|
self.assertEqual(proj.relpath, "tests/path1")
|
|
|
|
if proj.name == "project2":
|
|
|
|
found_proj2 = True
|
|
|
|
self.assertNotEqual(proj.name, "project3")
|
|
|
|
self.assertNotEqual(proj.name, "project4")
|
|
|
|
self.assertNotEqual(proj.name, "project5")
|
|
|
|
self.assertNotEqual(proj.name, "project6")
|
|
|
|
self.assertTrue(found_proj1_path1)
|
|
|
|
self.assertTrue(found_proj2)
|
|
|
|
|
2024-09-09 13:54:57 +00:00
|
|
|
def test_base_revision_checks_on_patching(self):
|
|
|
|
manifest_fail_wrong_tag = self.getXmlManifest(
|
|
|
|
"""
|
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="tag.002" />
|
|
|
|
<project name="project1" path="tests/path1" />
|
|
|
|
<extend-project name="project1" revision="new_hash" base-rev="tag.001" />
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
with self.assertRaises(error.ManifestParseError):
|
|
|
|
manifest_fail_wrong_tag.ToXml()
|
|
|
|
|
|
|
|
manifest_fail_remove = self.getXmlManifest(
|
|
|
|
"""
|
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="project1" path="tests/path1" revision="hash1" />
|
|
|
|
<remove-project name="project1" base-rev="wrong_hash" />
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
with self.assertRaises(error.ManifestParseError):
|
|
|
|
manifest_fail_remove.ToXml()
|
|
|
|
|
|
|
|
manifest_fail_extend = self.getXmlManifest(
|
|
|
|
"""
|
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="project1" path="tests/path1" revision="hash1" />
|
|
|
|
<extend-project name="project1" revision="new_hash" base-rev="wrong_hash" />
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
with self.assertRaises(error.ManifestParseError):
|
|
|
|
manifest_fail_extend.ToXml()
|
|
|
|
|
|
|
|
manifest_fail_unknown = self.getXmlManifest(
|
|
|
|
"""
|
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="project1" path="tests/path1" />
|
|
|
|
<extend-project name="project1" revision="new_hash" base-rev="any_hash" />
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
with self.assertRaises(error.ManifestParseError):
|
|
|
|
manifest_fail_unknown.ToXml()
|
|
|
|
|
|
|
|
manifest_ok = self.getXmlManifest(
|
|
|
|
"""
|
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="project1" path="tests/path1" revision="hash1" />
|
|
|
|
<project name="project2" path="tests/path2" revision="hash2" />
|
|
|
|
<project name="project3" path="tests/path3" revision="hash3" />
|
|
|
|
<project name="project4" path="tests/path4" revision="hash4" />
|
|
|
|
|
|
|
|
<remove-project name="project1" />
|
|
|
|
<remove-project name="project2" base-rev="hash2" />
|
|
|
|
<project name="project2" path="tests/path2" revision="new_hash2" />
|
|
|
|
<extend-project name="project3" base-rev="hash3" revision="new_hash3" />
|
|
|
|
<extend-project name="project3" base-rev="new_hash3" revision="newer_hash3" />
|
|
|
|
<remove-project path="tests/path4" base-rev="hash4" />
|
|
|
|
</manifest>
|
|
|
|
"""
|
|
|
|
)
|
|
|
|
found_proj2 = False
|
|
|
|
found_proj3 = False
|
|
|
|
for proj in manifest_ok.projects:
|
|
|
|
if proj.name == "project2":
|
|
|
|
found_proj2 = True
|
|
|
|
if proj.name == "project3":
|
|
|
|
found_proj3 = True
|
|
|
|
self.assertNotEqual(proj.name, "project1")
|
|
|
|
self.assertNotEqual(proj.name, "project4")
|
|
|
|
self.assertTrue(found_proj2)
|
|
|
|
self.assertTrue(found_proj3)
|
|
|
|
self.assertTrue(len(manifest_ok.projects) == 2)
|
|
|
|
|
2020-06-13 09:10:40 +00:00
|
|
|
|
|
|
|
class ExtendProjectElementTests(ManifestParseTestCase):
|
2023-03-11 06:46:20 +00:00
|
|
|
"""Tests for <extend-project>."""
|
2020-06-13 09:10:40 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_extend_project_dest_path_single_match(self):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2020-06-13 09:10:40 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="myproject" />
|
|
|
|
<extend-project name="myproject" dest-path="bar" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(len(manifest.projects), 1)
|
|
|
|
self.assertEqual(manifest.projects[0].relpath, "bar")
|
|
|
|
|
|
|
|
def test_extend_project_dest_path_multi_match(self):
|
|
|
|
with self.assertRaises(manifest_xml.ManifestParseError):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2020-06-13 09:10:40 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="myproject" path="x" />
|
|
|
|
<project name="myproject" path="y" />
|
|
|
|
<extend-project name="myproject" dest-path="bar" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
manifest.projects
|
2020-06-13 09:10:40 +00:00
|
|
|
|
2023-03-11 06:46:20 +00:00
|
|
|
def test_extend_project_dest_path_multi_match_path_specified(self):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2020-06-13 09:10:40 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="myproject" path="x" />
|
|
|
|
<project name="myproject" path="y" />
|
|
|
|
<extend-project name="myproject" path="x" dest-path="bar" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(len(manifest.projects), 2)
|
|
|
|
if manifest.projects[0].relpath == "y":
|
|
|
|
self.assertEqual(manifest.projects[1].relpath, "bar")
|
|
|
|
else:
|
|
|
|
self.assertEqual(manifest.projects[0].relpath, "bar")
|
|
|
|
self.assertEqual(manifest.projects[1].relpath, "y")
|
|
|
|
|
|
|
|
def test_extend_project_dest_branch(self):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2022-09-09 15:13:17 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" dest-branch="foo" />
|
|
|
|
<project name="myproject" />
|
|
|
|
<extend-project name="myproject" dest-branch="bar" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
""" # noqa: E501
|
|
|
|
)
|
|
|
|
self.assertEqual(len(manifest.projects), 1)
|
|
|
|
self.assertEqual(manifest.projects[0].dest_branch, "bar")
|
|
|
|
|
|
|
|
def test_extend_project_upstream(self):
|
|
|
|
manifest = self.getXmlManifest(
|
|
|
|
"""
|
2022-09-09 15:13:17 +00:00
|
|
|
<manifest>
|
|
|
|
<remote name="default-remote" fetch="http://localhost" />
|
|
|
|
<default remote="default-remote" revision="refs/heads/main" />
|
|
|
|
<project name="myproject" />
|
|
|
|
<extend-project name="myproject" upstream="bar" />
|
|
|
|
</manifest>
|
2023-03-11 06:46:20 +00:00
|
|
|
"""
|
|
|
|
)
|
|
|
|
self.assertEqual(len(manifest.projects), 1)
|
|
|
|
self.assertEqual(manifest.projects[0].upstream, "bar")
|
2023-09-19 16:51:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
class NormalizeUrlTests(ManifestParseTestCase):
|
|
|
|
"""Tests for normalize_url() in manifest_xml.py"""
|
|
|
|
|
|
|
|
def test_has_trailing_slash(self):
|
|
|
|
url = "http://foo.com/bar/baz/"
|
|
|
|
self.assertEqual(
|
|
|
|
"http://foo.com/bar/baz", manifest_xml.normalize_url(url)
|
|
|
|
)
|
|
|
|
|
2023-12-18 09:25:16 +00:00
|
|
|
url = "http://foo.com/bar/"
|
|
|
|
self.assertEqual("http://foo.com/bar", manifest_xml.normalize_url(url))
|
|
|
|
|
2023-12-18 21:31:11 +00:00
|
|
|
def test_has_leading_slash(self):
|
|
|
|
"""SCP-like syntax except a / comes before the : which git disallows."""
|
|
|
|
url = "/git@foo.com:bar/baf"
|
|
|
|
self.assertEqual(url, manifest_xml.normalize_url(url))
|
|
|
|
|
|
|
|
url = "gi/t@foo.com:bar/baf"
|
|
|
|
self.assertEqual(url, manifest_xml.normalize_url(url))
|
|
|
|
|
|
|
|
url = "git@fo/o.com:bar/baf"
|
|
|
|
self.assertEqual(url, manifest_xml.normalize_url(url))
|
|
|
|
|
2023-09-19 16:51:03 +00:00
|
|
|
def test_has_no_scheme(self):
|
|
|
|
"""Deal with cases where we have no scheme, but we also
|
|
|
|
aren't dealing with the git SCP-like syntax
|
|
|
|
"""
|
|
|
|
url = "foo.com/baf/bat"
|
|
|
|
self.assertEqual(url, manifest_xml.normalize_url(url))
|
|
|
|
|
2023-12-18 09:25:16 +00:00
|
|
|
url = "foo.com/baf"
|
|
|
|
self.assertEqual(url, manifest_xml.normalize_url(url))
|
|
|
|
|
2023-09-19 16:51:03 +00:00
|
|
|
url = "git@foo.com/baf/bat"
|
|
|
|
self.assertEqual(url, manifest_xml.normalize_url(url))
|
|
|
|
|
2023-12-18 09:25:16 +00:00
|
|
|
url = "git@foo.com/baf"
|
|
|
|
self.assertEqual(url, manifest_xml.normalize_url(url))
|
|
|
|
|
2023-09-19 16:51:03 +00:00
|
|
|
url = "/file/path/here"
|
|
|
|
self.assertEqual(url, manifest_xml.normalize_url(url))
|
|
|
|
|
|
|
|
def test_has_no_scheme_matches_scp_like_syntax(self):
|
|
|
|
url = "git@foo.com:bar/baf"
|
|
|
|
self.assertEqual(
|
|
|
|
"ssh://git@foo.com/bar/baf", manifest_xml.normalize_url(url)
|
|
|
|
)
|
2023-12-18 09:25:16 +00:00
|
|
|
|
|
|
|
url = "git@foo.com:bar/"
|
|
|
|
self.assertEqual(
|
|
|
|
"ssh://git@foo.com/bar", manifest_xml.normalize_url(url)
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_remote_url_resolution(self):
|
|
|
|
remote = manifest_xml._XmlRemote(
|
|
|
|
name="foo",
|
|
|
|
fetch="git@github.com:org2/",
|
|
|
|
manifestUrl="git@github.com:org2/custom_manifest.git",
|
|
|
|
)
|
|
|
|
self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl)
|
|
|
|
|
|
|
|
remote = manifest_xml._XmlRemote(
|
|
|
|
name="foo",
|
|
|
|
fetch="ssh://git@github.com/org2/",
|
|
|
|
manifestUrl="git@github.com:org2/custom_manifest.git",
|
|
|
|
)
|
|
|
|
self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl)
|
|
|
|
|
|
|
|
remote = manifest_xml._XmlRemote(
|
|
|
|
name="foo",
|
|
|
|
fetch="git@github.com:org2/",
|
|
|
|
manifestUrl="ssh://git@github.com/org2/custom_manifest.git",
|
|
|
|
)
|
|
|
|
self.assertEqual("ssh://git@github.com/org2", remote.resolvedFetchUrl)
|