summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2021-02-18 23:28:32 -0500
committerMike Frysinger <vapier@google.com>2021-02-19 20:06:03 +0000
commit057905fa1d074e6dd341822e5a6a1e49b6b97a21 (patch)
tree4fe23676208a9c0145e08879db4f0b9b8e5268a9
parent401c6f072564966437a74dc2f33280a85d79dc84 (diff)
downloadgit-repo-057905fa1d074e6dd341822e5a6a1e49b6b97a21.tar.gz
error: fix pickling of all exceptions
Make sure all our custom exceptions can be pickled so that if they get thrown in a multiprocess subprocess, we don't crash & hang due to multiprocessing being unable to pickle+unpickle the exception. Details/examples can be seen in Python reports like: https://bugs.python.org/issue13751 Change-Id: Iddf14d3952ad4e2867cfc71891d6b6559130df4b Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/297382 Reviewed-by: Michael Mortensen <mmortensen@google.com> Tested-by: Mike Frysinger <vapier@google.com>
-rw-r--r--error.py16
-rw-r--r--tests/test_error.py53
2 files changed, 61 insertions, 8 deletions
diff --git a/error.py b/error.py
index 8bb64b8f..20d5f725 100644
--- a/error.py
+++ b/error.py
@@ -37,7 +37,7 @@ class NoManifestException(Exception):
37 """ 37 """
38 38
39 def __init__(self, path, reason): 39 def __init__(self, path, reason):
40 super(NoManifestException, self).__init__() 40 super(NoManifestException, self).__init__(path, reason)
41 self.path = path 41 self.path = path
42 self.reason = reason 42 self.reason = reason
43 43
@@ -50,7 +50,7 @@ class EditorError(Exception):
50 """ 50 """
51 51
52 def __init__(self, reason): 52 def __init__(self, reason):
53 super(EditorError, self).__init__() 53 super(EditorError, self).__init__(reason)
54 self.reason = reason 54 self.reason = reason
55 55
56 def __str__(self): 56 def __str__(self):
@@ -62,7 +62,7 @@ class GitError(Exception):
62 """ 62 """
63 63
64 def __init__(self, command): 64 def __init__(self, command):
65 super(GitError, self).__init__() 65 super(GitError, self).__init__(command)
66 self.command = command 66 self.command = command
67 67
68 def __str__(self): 68 def __str__(self):
@@ -74,7 +74,7 @@ class UploadError(Exception):
74 """ 74 """
75 75
76 def __init__(self, reason): 76 def __init__(self, reason):
77 super(UploadError, self).__init__() 77 super(UploadError, self).__init__(reason)
78 self.reason = reason 78 self.reason = reason
79 79
80 def __str__(self): 80 def __str__(self):
@@ -86,7 +86,7 @@ class DownloadError(Exception):
86 """ 86 """
87 87
88 def __init__(self, reason): 88 def __init__(self, reason):
89 super(DownloadError, self).__init__() 89 super(DownloadError, self).__init__(reason)
90 self.reason = reason 90 self.reason = reason
91 91
92 def __str__(self): 92 def __str__(self):
@@ -98,7 +98,7 @@ class NoSuchProjectError(Exception):
98 """ 98 """
99 99
100 def __init__(self, name=None): 100 def __init__(self, name=None):
101 super(NoSuchProjectError, self).__init__() 101 super(NoSuchProjectError, self).__init__(name)
102 self.name = name 102 self.name = name
103 103
104 def __str__(self): 104 def __str__(self):
@@ -112,7 +112,7 @@ class InvalidProjectGroupsError(Exception):
112 """ 112 """
113 113
114 def __init__(self, name=None): 114 def __init__(self, name=None):
115 super(InvalidProjectGroupsError, self).__init__() 115 super(InvalidProjectGroupsError, self).__init__(name)
116 self.name = name 116 self.name = name
117 117
118 def __str__(self): 118 def __str__(self):
@@ -128,7 +128,7 @@ class RepoChangedException(Exception):
128 """ 128 """
129 129
130 def __init__(self, extra_args=None): 130 def __init__(self, extra_args=None):
131 super(RepoChangedException, self).__init__() 131 super(RepoChangedException, self).__init__(extra_args)
132 self.extra_args = extra_args or [] 132 self.extra_args = extra_args or []
133 133
134 134
diff --git a/tests/test_error.py b/tests/test_error.py
new file mode 100644
index 00000000..82b00c24
--- /dev/null
+++ b/tests/test_error.py
@@ -0,0 +1,53 @@
1# Copyright 2021 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15"""Unittests for the error.py module."""
16
17import inspect
18import pickle
19import unittest
20
21import error
22
23
24class PickleTests(unittest.TestCase):
25 """Make sure all our custom exceptions can be pickled."""
26
27 def getExceptions(self):
28 """Return all our custom exceptions."""
29 for name in dir(error):
30 cls = getattr(error, name)
31 if isinstance(cls, type) and issubclass(cls, Exception):
32 yield cls
33
34 def testExceptionLookup(self):
35 """Make sure our introspection logic works."""
36 classes = list(self.getExceptions())
37 self.assertIn(error.HookError, classes)
38 # Don't assert the exact number to avoid being a change-detector test.
39 self.assertGreater(len(classes), 10)
40
41 def testPickle(self):
42 """Try to pickle all the exceptions."""
43 for cls in self.getExceptions():
44 args = inspect.getfullargspec(cls.__init__).args[1:]
45 obj = cls(*args)
46 p = pickle.dumps(obj)
47 try:
48 newobj = pickle.loads(p)
49 except Exception as e: # pylint: disable=broad-except
50 self.fail('Class %s is unable to be pickled: %s\n'
51 'Incomplete super().__init__(...) call?' % (cls, e))
52 self.assertIsInstance(newobj, cls)
53 self.assertEqual(str(obj), str(newobj))