diff options
author | Mike Frysinger <vapier@google.com> | 2020-02-22 05:30:12 -0500 |
---|---|---|
committer | Mike Frysinger <vapier@google.com> | 2020-03-13 18:48:52 +0000 |
commit | bb8ee7f54a4e0bf07d466304d800f13ec41ac21c (patch) | |
tree | af0dec0603951b94f2cb7a73d273edd486df0fbf | |
parent | 23d7dafd10e382f42c34618f7c86b3ff1c9d4245 (diff) | |
download | git-repo-bb8ee7f54a4e0bf07d466304d800f13ec41ac21c.tar.gz |
manifest_xml: unify bool & int parsing
We've been overly lenient with boolean parsing by ignoring invalid
values as "false" even if the user didn't intend that. Turn all
unknown values into warnings to avoid breaking existing manifests,
and unify the parsing logic in a helper to simplify.
We've been stricter about numbers, but still copying & pasting
inconsistent code. Add a helper for this too. For out of range
sync-j numbers (i.e. less than 1), throw a warning for now, but
mark it for future hard failures.
Change-Id: I924162b8036e6a5f1e31b6ebb24b6a26ed63712d
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/256457
Reviewed-by: Michael Mortensen <mmortensen@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
-rw-r--r-- | manifest_xml.py | 125 | ||||
-rw-r--r-- | tests/test_manifest_xml.py | 57 |
2 files changed, 127 insertions, 55 deletions
diff --git a/manifest_xml.py b/manifest_xml.py index fe09f498..edcbadae 100644 --- a/manifest_xml.py +++ b/manifest_xml.py | |||
@@ -57,6 +57,60 @@ urllib.parse.uses_netloc.extend([ | |||
57 | 'rpc']) | 57 | 'rpc']) |
58 | 58 | ||
59 | 59 | ||
60 | def XmlBool(node, attr, default=None): | ||
61 | """Determine boolean value of |node|'s |attr|. | ||
62 | |||
63 | Invalid values will issue a non-fatal warning. | ||
64 | |||
65 | Args: | ||
66 | node: XML node whose attributes we access. | ||
67 | attr: The attribute to access. | ||
68 | default: If the attribute is not set (value is empty), then use this. | ||
69 | |||
70 | Returns: | ||
71 | True if the attribute is a valid string representing true. | ||
72 | False if the attribute is a valid string representing false. | ||
73 | |default| otherwise. | ||
74 | """ | ||
75 | value = node.getAttribute(attr) | ||
76 | s = value.lower() | ||
77 | if s == '': | ||
78 | return default | ||
79 | elif s in {'yes', 'true', '1'}: | ||
80 | return True | ||
81 | elif s in {'no', 'false', '0'}: | ||
82 | return False | ||
83 | else: | ||
84 | print('warning: manifest: %s="%s": ignoring invalid XML boolean' % | ||
85 | (attr, value), file=sys.stderr) | ||
86 | return default | ||
87 | |||
88 | |||
89 | def XmlInt(node, attr, default=None): | ||
90 | """Determine integer value of |node|'s |attr|. | ||
91 | |||
92 | Args: | ||
93 | node: XML node whose attributes we access. | ||
94 | attr: The attribute to access. | ||
95 | default: If the attribute is not set (value is empty), then use this. | ||
96 | |||
97 | Returns: | ||
98 | The number if the attribute is a valid number. | ||
99 | |||
100 | Raises: | ||
101 | ManifestParseError: The number is invalid. | ||
102 | """ | ||
103 | value = node.getAttribute(attr) | ||
104 | if not value: | ||
105 | return default | ||
106 | |||
107 | try: | ||
108 | return int(value) | ||
109 | except ValueError: | ||
110 | raise ManifestParseError('manifest: invalid %s="%s" integer' % | ||
111 | (attr, value)) | ||
112 | |||
113 | |||
60 | class _Default(object): | 114 | class _Default(object): |
61 | """Project defaults within the manifest.""" | 115 | """Project defaults within the manifest.""" |
62 | 116 | ||
@@ -757,29 +811,14 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
757 | d.destBranchExpr = node.getAttribute('dest-branch') or None | 811 | d.destBranchExpr = node.getAttribute('dest-branch') or None |
758 | d.upstreamExpr = node.getAttribute('upstream') or None | 812 | d.upstreamExpr = node.getAttribute('upstream') or None |
759 | 813 | ||
760 | sync_j = node.getAttribute('sync-j') | 814 | d.sync_j = XmlInt(node, 'sync-j', 1) |
761 | if sync_j == '' or sync_j is None: | 815 | if d.sync_j <= 0: |
762 | d.sync_j = 1 | 816 | raise ManifestParseError('%s: sync-j must be greater than 0, not "%s"' % |
763 | else: | 817 | (self.manifestFile, d.sync_j)) |
764 | d.sync_j = int(sync_j) | ||
765 | |||
766 | sync_c = node.getAttribute('sync-c') | ||
767 | if not sync_c: | ||
768 | d.sync_c = False | ||
769 | else: | ||
770 | d.sync_c = sync_c.lower() in ("yes", "true", "1") | ||
771 | |||
772 | sync_s = node.getAttribute('sync-s') | ||
773 | if not sync_s: | ||
774 | d.sync_s = False | ||
775 | else: | ||
776 | d.sync_s = sync_s.lower() in ("yes", "true", "1") | ||
777 | 818 | ||
778 | sync_tags = node.getAttribute('sync-tags') | 819 | d.sync_c = XmlBool(node, 'sync-c', False) |
779 | if not sync_tags: | 820 | d.sync_s = XmlBool(node, 'sync-s', False) |
780 | d.sync_tags = True | 821 | d.sync_tags = XmlBool(node, 'sync-tags', True) |
781 | else: | ||
782 | d.sync_tags = sync_tags.lower() in ("yes", "true", "1") | ||
783 | return d | 822 | return d |
784 | 823 | ||
785 | def _ParseNotice(self, node): | 824 | def _ParseNotice(self, node): |
@@ -856,39 +895,15 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
856 | raise ManifestParseError("project %s path cannot be absolute in %s" % | 895 | raise ManifestParseError("project %s path cannot be absolute in %s" % |
857 | (name, self.manifestFile)) | 896 | (name, self.manifestFile)) |
858 | 897 | ||
859 | rebase = node.getAttribute('rebase') | 898 | rebase = XmlBool(node, 'rebase', True) |
860 | if not rebase: | 899 | sync_c = XmlBool(node, 'sync-c', False) |
861 | rebase = True | 900 | sync_s = XmlBool(node, 'sync-s', self._default.sync_s) |
862 | else: | 901 | sync_tags = XmlBool(node, 'sync-tags', self._default.sync_tags) |
863 | rebase = rebase.lower() in ("yes", "true", "1") | ||
864 | |||
865 | sync_c = node.getAttribute('sync-c') | ||
866 | if not sync_c: | ||
867 | sync_c = False | ||
868 | else: | ||
869 | sync_c = sync_c.lower() in ("yes", "true", "1") | ||
870 | |||
871 | sync_s = node.getAttribute('sync-s') | ||
872 | if not sync_s: | ||
873 | sync_s = self._default.sync_s | ||
874 | else: | ||
875 | sync_s = sync_s.lower() in ("yes", "true", "1") | ||
876 | |||
877 | sync_tags = node.getAttribute('sync-tags') | ||
878 | if not sync_tags: | ||
879 | sync_tags = self._default.sync_tags | ||
880 | else: | ||
881 | sync_tags = sync_tags.lower() in ("yes", "true", "1") | ||
882 | 902 | ||
883 | clone_depth = node.getAttribute('clone-depth') | 903 | clone_depth = XmlInt(node, 'clone-depth') |
884 | if clone_depth: | 904 | if clone_depth is not None and clone_depth <= 0: |
885 | try: | 905 | raise ManifestParseError('%s: clone-depth must be greater than 0, not "%s"' % |
886 | clone_depth = int(clone_depth) | 906 | (self.manifestFile, clone_depth)) |
887 | if clone_depth <= 0: | ||
888 | raise ValueError() | ||
889 | except ValueError: | ||
890 | raise ManifestParseError('invalid clone-depth %s in %s' % | ||
891 | (clone_depth, self.manifestFile)) | ||
892 | 907 | ||
893 | dest_branch = node.getAttribute('dest-branch') or self._default.destBranchExpr | 908 | dest_branch = node.getAttribute('dest-branch') or self._default.destBranchExpr |
894 | 909 | ||
@@ -911,7 +926,7 @@ https://gerrit.googlesource.com/git-repo/+/HEAD/docs/manifest-format.md | |||
911 | groups.extend(set(default_groups).difference(groups)) | 926 | groups.extend(set(default_groups).difference(groups)) |
912 | 927 | ||
913 | if self.IsMirror and node.hasAttribute('force-path'): | 928 | if self.IsMirror and node.hasAttribute('force-path'): |
914 | if node.getAttribute('force-path').lower() in ("yes", "true", "1"): | 929 | if XmlBool(node, 'force-path', False): |
915 | gitdir = os.path.join(self.topdir, '%s.git' % path) | 930 | gitdir = os.path.join(self.topdir, '%s.git' % path) |
916 | 931 | ||
917 | project = Project(manifest=self, | 932 | project = Project(manifest=self, |
diff --git a/tests/test_manifest_xml.py b/tests/test_manifest_xml.py index 1cb72971..aa6cb7df 100644 --- a/tests/test_manifest_xml.py +++ b/tests/test_manifest_xml.py | |||
@@ -20,6 +20,7 @@ from __future__ import print_function | |||
20 | 20 | ||
21 | import os | 21 | import os |
22 | import unittest | 22 | import unittest |
23 | import xml.dom.minidom | ||
23 | 24 | ||
24 | import error | 25 | import error |
25 | import manifest_xml | 26 | import manifest_xml |
@@ -89,3 +90,59 @@ class ManifestValidateFilePaths(unittest.TestCase): | |||
89 | error.ManifestInvalidPathError, self.check_both, path, 'a') | 90 | error.ManifestInvalidPathError, self.check_both, path, 'a') |
90 | self.assertRaises( | 91 | self.assertRaises( |
91 | error.ManifestInvalidPathError, self.check_both, 'a', path) | 92 | error.ManifestInvalidPathError, self.check_both, 'a', path) |
93 | |||
94 | |||
95 | class ValueTests(unittest.TestCase): | ||
96 | """Check utility parsing code.""" | ||
97 | |||
98 | def _get_node(self, text): | ||
99 | return xml.dom.minidom.parseString(text).firstChild | ||
100 | |||
101 | def test_bool_default(self): | ||
102 | """Check XmlBool default handling.""" | ||
103 | node = self._get_node('<node/>') | ||
104 | self.assertIsNone(manifest_xml.XmlBool(node, 'a')) | ||
105 | self.assertIsNone(manifest_xml.XmlBool(node, 'a', None)) | ||
106 | self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123)) | ||
107 | |||
108 | node = self._get_node('<node a=""/>') | ||
109 | self.assertIsNone(manifest_xml.XmlBool(node, 'a')) | ||
110 | |||
111 | def test_bool_invalid(self): | ||
112 | """Check XmlBool invalid handling.""" | ||
113 | node = self._get_node('<node a="moo"/>') | ||
114 | self.assertEqual(123, manifest_xml.XmlBool(node, 'a', 123)) | ||
115 | |||
116 | def test_bool_true(self): | ||
117 | """Check XmlBool true values.""" | ||
118 | for value in ('yes', 'true', '1'): | ||
119 | node = self._get_node('<node a="%s"/>' % (value,)) | ||
120 | self.assertTrue(manifest_xml.XmlBool(node, 'a')) | ||
121 | |||
122 | def test_bool_false(self): | ||
123 | """Check XmlBool false values.""" | ||
124 | for value in ('no', 'false', '0'): | ||
125 | node = self._get_node('<node a="%s"/>' % (value,)) | ||
126 | self.assertFalse(manifest_xml.XmlBool(node, 'a')) | ||
127 | |||
128 | def test_int_default(self): | ||
129 | """Check XmlInt default handling.""" | ||
130 | node = self._get_node('<node/>') | ||
131 | self.assertIsNone(manifest_xml.XmlInt(node, 'a')) | ||
132 | self.assertIsNone(manifest_xml.XmlInt(node, 'a', None)) | ||
133 | self.assertEqual(123, manifest_xml.XmlInt(node, 'a', 123)) | ||
134 | |||
135 | node = self._get_node('<node a=""/>') | ||
136 | self.assertIsNone(manifest_xml.XmlInt(node, 'a')) | ||
137 | |||
138 | def test_int_good(self): | ||
139 | """Check XmlInt numeric handling.""" | ||
140 | for value in (-1, 0, 1, 50000): | ||
141 | node = self._get_node('<node a="%s"/>' % (value,)) | ||
142 | self.assertEqual(value, manifest_xml.XmlInt(node, 'a')) | ||
143 | |||
144 | def test_int_invalid(self): | ||
145 | """Check XmlInt invalid handling.""" | ||
146 | with self.assertRaises(error.ManifestParseError): | ||
147 | node = self._get_node('<node a="xx"/>') | ||
148 | manifest_xml.XmlInt(node, 'a') | ||