Merge lp:~zyga/checkbox/better-plugins into lp:checkbox
- better-plugins
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Zygmunt Krynicki |
Approved revision: | 2583 |
Merged at revision: | 2573 |
Proposed branch: | lp:~zyga/checkbox/better-plugins |
Merge into: | lp:checkbox |
Diff against target: |
697 lines (+425/-51) 2 files modified
plainbox/plainbox/impl/secure/plugins.py (+127/-22) plainbox/plainbox/impl/secure/test_plugins.py (+298/-29) |
To merge this branch: | bzr merge lp:~zyga/checkbox/better-plugins |
Related bugs: | |
Related blueprints: |
Let's use Xmas to fix checkbox bugs
(Undefined)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Zygmunt Krynicki (community) | Approve | ||
Brendan Donegan (community) | Approve | ||
Review via email: mp+199324@code.launchpad.net |
Commit message
Description of the change
5d891e0 plainbox:
77a2935 plainbox:
1d869c8 plainbox:
c501ccb plainbox:
2566d72 plainbox:
6a57cef plainbox:
541c2f2 plainbox:
5e0fd54 plainbox:
aba51ed plainbox:
0fc1159 plainbox:
4a317ea plainbox:
a9244ca plainbox:
Assorted improvements to plugins
Daniel Manrique (roadmr) wrote : | # |
Looks OK to me, this is pretty foundational and I'd like to see how this is used, but as a starting point it's fine. Also looking at upcoming tests would make it easier to see how it all fits together.
Zygmunt Krynicki (zyga) wrote : | # |
I have all the usage code written now. I'll post it tomorrow in the
load-all-jobs branch.
On Tue, Dec 17, 2013 at 10:33 PM, Daniel Manrique
<email address hidden> wrote:
> Looks OK to me, this is pretty foundational and I'd like to see how this is used, but as a starting point it's fine. Also looking at upcoming tests would make it easier to see how it all fits together.
> --
> https:/
> You are the owner of lp:~zkrynicki/checkbox/better-plugins.
Zygmunt Krynicki (zyga) wrote : | # |
Added lumps of tests, fixed some iffy bugs :-)
Brendan Donegan (brendan-donegan) wrote : | # |
Looks good, +1
Daniel Manrique (roadmr) wrote : | # |
The attempt to merge lp:~zkrynicki/checkbox/better-plugins into lp:checkbox failed. Below is the output from the failed tests.
[precise] Bringing VM 'up'
[precise] (timing) 8.91user 3.90system 5:45.47elapsed 3%CPU (0avgtext+0avgdata 20960maxresident)k
[precise] (timing) 0inputs+256outputs (0major+
[precise] Starting tests...
[precise] Checkbox GUI build: [32;1mPASS[39;0m
[precise] (timing) 1.12user 0.48system 1:03.72elapsed 2%CPU (0avgtext+0avgdata 20240maxresident)k
[precise] (timing) 7336inputs+
[precise] CheckBox test suite: [32;1mPASS[39;0m
[precise] (timing) 0.76user 0.30system 0:37.35elapsed 2%CPU (0avgtext+0avgdata 20216maxresident)k
[precise] (timing) 0inputs+40outputs (0major+
[precise] (timing) 0.73user 0.32system 0:05.73elapsed 18%CPU (0avgtext+0avgdata 20628maxresident)k
[precise] (timing) 0inputs+16outputs (0major+
[precise] PlainBox test suite: [31;1mFAIL[39;0m
[precise] stdout: http://
[precise] stderr: http://
[precise] (timing) Command exited with non-zero status 1
[precise] (timing) 0.98user 0.30system 0:13.85elapsed 9%CPU (0avgtext+0avgdata 22184maxresident)k
[precise] (timing) 0inputs+592outputs (0major+
[precise] PlainBox documentation build: [32;1mPASS[39;0m
[precise] (timing) 0.93user 0.29system 0:25.96elapsed 4%CPU (0avgtext+0avgdata 23568maxresident)k
[precise] (timing) 0inputs+968outputs (0major+
[precise] CheckBoxNG test suite: [31;1mFAIL[39;0m
[precise] stdout: http://
[precise] stderr: http://
[precise] (timing) Command exited with non-zero status 1
[precise] (timing) 0.80user 0.28system 0:06.72elapsed 16%CPU (0avgtext+0avgdata 19704maxresident)k
[precise] (timing) 0inputs+24outputs (0major+
[precise] Integration tests: [31;1mFAIL[39;0m
[precise] stdout:
[precise] stderr: http://
[precise] (timing) Command exited with non-zero status 1
[precise] (timing) 0.72user 0.29system 0:05.49elapsed 18%CPU (0avgtext+0avgdata 20464maxresident)k
[precise] (timing) 0inputs+8outputs (0major+
[precise] Destroying VM
[quantal] Bringing VM 'up'
[quantal] (timing) 8.06user 3.67system 4:40.75elapsed 4%CPU (0avgtext+0avgdata 21280maxresident)k
[quantal] (timing) 16inputs+232outputs (1major+
[quantal] Starting tests...
[quantal] Checkbox GUI build: [32;1mPASS[39;0m
[quantal] (timing) 0.84user 0.27system 1:08.02elapsed 1%CPU (0avgtext+0avgdata 19808maxresident)k
[quantal] (timing) 0inputs+64outputs (0major+
[quantal] CheckBox test suite: [32;1mPASS[39;0m
[quantal] (timing) 0.81user 0.31system 0:41.79elapsed 2%CPU (0avgtext+0avgdata 19808maxresident)k
[quantal] (timing) 0inputs+40outputs (0major+
[quantal] (timing) 0.72user 0.29system 0:08.18elapsed 12%CPU (0avgtext+0avgdata 20632maxresident)k
[quantal] (timing) 0inputs+16outputs (0major+
[quantal...
Zygmunt Krynicki (zyga) wrote : | # |
Ah, so collections.abc is python3.3 only, I've read the docs for that module and it seems it used to be a part of collections directly so I've changed references refrences ``collections.
Trying again?
Daniel Manrique (roadmr) wrote : | # |
The attempt to merge lp:~zkrynicki/checkbox/better-plugins into lp:checkbox failed. Below is the output from the failed tests.
[precise] Bringing VM 'up'
[precise] (timing) 8.83user 3.86system 5:45.82elapsed 3%CPU (0avgtext+0avgdata 21460maxresident)k
[precise] (timing) 8inputs+240outputs (1major+
[precise] Starting tests...
[precise] Checkbox GUI build: [32;1mPASS[39;0m
[precise] (timing) 0.81user 0.26system 1:03.47elapsed 1%CPU (0avgtext+0avgdata 19940maxresident)k
[precise] (timing) 24inputs+56outputs (0major+
[precise] CheckBox test suite: [32;1mPASS[39;0m
[precise] (timing) 0.83user 0.29system 0:37.48elapsed 3%CPU (0avgtext+0avgdata 19936maxresident)k
[precise] (timing) 0inputs+48outputs (0major+
[precise] (timing) 0.74user 0.28system 0:05.68elapsed 18%CPU (0avgtext+0avgdata 20156maxresident)k
[precise] (timing) 0inputs+16outputs (0major+
[precise] PlainBox test suite: [31;1mFAIL[39;0m
[precise] stdout: http://
[precise] stderr: http://
[precise] (timing) Command exited with non-zero status 1
[precise] (timing) 1.05user 0.28system 0:16.62elapsed 8%CPU (0avgtext+0avgdata 20672maxresident)k
[precise] (timing) 0inputs+312outputs (0major+
[precise] PlainBox documentation build: [32;1mPASS[39;0m
[precise] (timing) 0.78user 0.30system 0:33.72elapsed 3%CPU (0avgtext+0avgdata 20688maxresident)k
[precise] (timing) 0inputs+32outputs (0major+
[precise] CheckBoxNG test suite: [32;1mPASS[39;0m
[precise] (timing) 0.75user 0.27system 0:07.30elapsed 14%CPU (0avgtext+0avgdata 20524maxresident)k
[precise] (timing) 0inputs+16outputs (0major+
[precise] Integration tests: [32;1mPASS[39;0m
[precise] (timing) 0.80user 0.32system 0:07.68elapsed 14%CPU (0avgtext+0avgdata 20132maxresident)k
[precise] (timing) 0inputs+8outputs (0major+
[precise] Destroying VM
[quantal] Bringing VM 'up'
[quantal] (timing) 8.29user 3.74system 4:40.67elapsed 4%CPU (0avgtext+0avgdata 21004maxresident)k
[quantal] (timing) 0inputs+224outputs (0major+
[quantal] Starting tests...
[quantal] Checkbox GUI build: [32;1mPASS[39;0m
[quantal] (timing) 0.79user 0.28system 1:08.13elapsed 1%CPU (0avgtext+0avgdata 20308maxresident)k
[quantal] (timing) 792inputs+56outputs (19major+
[quantal] CheckBox test suite: [32;1mPASS[39;0m
[quantal] (timing) 1.06user 0.46system 0:41.21elapsed 3%CPU (0avgtext+0avgdata 19932maxresident)k
[quantal] (timing) 4576inputs+
[quantal] (timing) 0.79user 0.25system 0:08.35elapsed 12%CPU (0avgtext+0avgdata 20300maxresident)k
[quantal] (timing) 0inputs+16outputs (0major+
[quantal] PlainBox test suite: [31;1mFAIL[39;0m
[quantal] stdout: http://
[quantal] stderr: http://
[quantal] (timing) Command exited with non-zero status 1
[quantal] (timing) 1.09user 0.31system 0:23.71elapsed 5%CPU (0avgtext+0avgdata 2067...
Zygmunt Krynicki (zyga) wrote : | # |
So a small genuine problem that I think was masked by python3.3 and its new exception class hierarchy. I've corrected that (it still passes on 3.3) so let's see again.
Daniel Manrique (roadmr) wrote : | # |
The attempt to merge lp:~zkrynicki/checkbox/better-plugins into lp:checkbox failed. Below is the output from the failed tests.
[precise] Bringing VM 'up'
[precise] (timing) 9.20user 4.04system 6:06.51elapsed 3%CPU (0avgtext+0avgdata 22112maxresident)k
[precise] (timing) 8inputs+240outputs (1major+
[precise] Starting tests...
[precise] Checkbox GUI build: [32;1mPASS[39;0m
[precise] (timing) 1.02user 0.46system 1:15.98elapsed 1%CPU (0avgtext+0avgdata 20528maxresident)k
[precise] (timing) 5368inputs+
[precise] CheckBox test suite: [32;1mPASS[39;0m
[precise] (timing) 0.78user 0.40system 0:38.82elapsed 3%CPU (0avgtext+0avgdata 19940maxresident)k
[precise] (timing) 0inputs+40outputs (0major+
[precise] (timing) 0.79user 0.30system 0:05.80elapsed 18%CPU (0avgtext+0avgdata 19960maxresident)k
[precise] (timing) 0inputs+16outputs (0major+
[precise] PlainBox test suite: [31;1mFAIL[39;0m
[precise] stdout: http://
[precise] stderr: http://
[precise] (timing) Command exited with non-zero status 1
[precise] (timing) 1.10user 0.32system 0:17.36elapsed 8%CPU (0avgtext+0avgdata 20664maxresident)k
[precise] (timing) 0inputs+320outputs (0major+
[precise] PlainBox documentation build: [32;1mPASS[39;0m
[precise] (timing) 0.81user 0.30system 0:35.62elapsed 3%CPU (0avgtext+0avgdata 20520maxresident)k
[precise] (timing) 0inputs+32outputs (0major+
[precise] CheckBoxNG test suite: [32;1mPASS[39;0m
[precise] (timing) 0.77user 0.29system 0:07.41elapsed 14%CPU (0avgtext+0avgdata 20060maxresident)k
[precise] (timing) 0inputs+16outputs (0major+
[precise] Integration tests: [32;1mPASS[39;0m
[precise] (timing) 0.76user 0.28system 0:07.66elapsed 13%CPU (0avgtext+0avgdata 20492maxresident)k
[precise] (timing) 0inputs+8outputs (0major+
[precise] Destroying VM
[quantal] Bringing VM 'up'
[quantal] (timing) 8.22user 3.85system 4:38.93elapsed 4%CPU (0avgtext+0avgdata 21020maxresident)k
[quantal] (timing) 16inputs+224outputs (0major+
[quantal] Starting tests...
[quantal] Checkbox GUI build: [32;1mPASS[39;0m
[quantal] (timing) 0.87user 0.34system 1:08.38elapsed 1%CPU (0avgtext+0avgdata 20456maxresident)k
[quantal] (timing) 1840inputs+
[quantal] CheckBox test suite: [32;1mPASS[39;0m
[quantal] (timing) 0.78user 0.29system 0:40.66elapsed 2%CPU (0avgtext+0avgdata 20604maxresident)k
[quantal] (timing) 0inputs+40outputs (0major+
[quantal] (timing) 0.77user 0.26system 0:08.31elapsed 12%CPU (0avgtext+0avgdata 20608maxresident)k
[quantal] (timing) 0inputs+16outputs (0major+
[quantal] PlainBox test suite: [31;1mFAIL[39;0m
[quantal] stdout: http://
[quantal] stderr: http://
[quantal] (timing) Command exited with non-zero status 1
[quantal] (timing) 1.14user 0.25system 0:23.65elapsed 5%CPU (0avgtext+0avgdata 198...
- 2581. By Zygmunt Krynicki
-
plainbox:
secure: plugins: handle OSError in FSPlugInCollection This patch makes FSPlugInCollection handle both OSError and IOError
since IOError can happen in read (this was already handled) but OSError
can happen on open().Signed-off-by: Zygmunt Krynicki <email address hidden>
- 2582. By Zygmunt Krynicki
-
plainbox:
secure: plugins: support multiple extensions This patch adds support for multiple file extensions in
FsPlugInCollection. By passing any sequence-like object instead of a
plain string any of the extensions contained therein are accepted.Signed-off-by: Zygmunt Krynicki <email address hidden>
- 2583. By Zygmunt Krynicki
-
plainbox:
secure: plugins: unify fake_open() functions Signed-off-by: Zygmunt Krynicki <email address hidden>
Zygmunt Krynicki (zyga) wrote : | # |
The ping-pong continues, apparenetly there was a separate problem in FsPlugInCollection that dind't handle OSError. It now does so let's try again.
Preview Diff
1 | === modified file 'plainbox/plainbox/impl/secure/plugins.py' |
2 | --- plainbox/plainbox/impl/secure/plugins.py 2013-12-16 20:39:40 +0000 |
3 | +++ plainbox/plainbox/impl/secure/plugins.py 2013-12-20 13:53:55 +0000 |
4 | @@ -98,14 +98,20 @@ |
5 | |
6 | @property |
7 | def plugin_name(self): |
8 | + """ |
9 | + plugin name, arbitrary string |
10 | + """ |
11 | return self._name |
12 | |
13 | @property |
14 | def plugin_object(self): |
15 | + """ |
16 | + plugin object, arbitrary object |
17 | + """ |
18 | return self._obj |
19 | |
20 | |
21 | -class IPlugInCollection: |
22 | +class IPlugInCollection(metaclass=abc.ABCMeta): |
23 | """ |
24 | A collection of IPlugIn objects. |
25 | """ |
26 | @@ -129,11 +135,26 @@ |
27 | """ |
28 | |
29 | @abc.abstractmethod |
30 | + def get_all_plugin_objects(self): |
31 | + """ |
32 | + Get an list of plug-in objects |
33 | + |
34 | + This is a shortcut that gives fastest access to a list of |
35 | + :attr:`IPlugIn.plugin_object` of each loaded plugin. |
36 | + """ |
37 | + |
38 | + @abc.abstractmethod |
39 | def get_all_items(self): |
40 | """ |
41 | Get an iterator to a sequence of (name, plug-in) |
42 | """ |
43 | |
44 | + @abc.abstractproperty |
45 | + def problem_list(self): |
46 | + """ |
47 | + List of problems encountered while loading plugins |
48 | + """ |
49 | + |
50 | @abc.abstractmethod |
51 | def load(self): |
52 | """ |
53 | @@ -146,15 +167,18 @@ |
54 | |
55 | @abc.abstractmethod |
56 | @contextlib.contextmanager |
57 | - def fake_plugins(self, plugins): |
58 | + def fake_plugins(self, plugins, problem_list=None): |
59 | """ |
60 | Context manager for using fake list of plugins |
61 | |
62 | - :param plugins: list of PlugIn-alike objects |
63 | + :param plugins: |
64 | + list of PlugIn-alike objects |
65 | + :param problem_list: |
66 | + list of problems (exceptions) |
67 | |
68 | - The provided list of plugins overrides any previously loaded |
69 | - plugins and prevent loading any other, real, plugins. After |
70 | - the context manager exits the previous state is restored. |
71 | + The provided list of plugins and exceptions overrides any previously |
72 | + loaded plugins and prevent loading any other, real, plugins. After the |
73 | + context manager exits the previous state is restored. |
74 | """ |
75 | |
76 | |
77 | @@ -164,7 +188,8 @@ |
78 | PlugInCollection implemenetations. |
79 | """ |
80 | |
81 | - def __init__(self, load=False, wrapper=PlugIn): |
82 | + def __init__(self, load=False, wrapper=PlugIn, *wrapper_args, |
83 | + **wrapper_kwargs): |
84 | """ |
85 | Initialize a collection of plug-ins |
86 | |
87 | @@ -172,68 +197,129 @@ |
88 | if true, load all of the plug-ins now |
89 | :param wrapper: |
90 | wrapper class for all loaded objects, defaults to :class:`PlugIn` |
91 | + :param wrapper_args: |
92 | + additional arguments passed to each instantiated wrapper |
93 | + :param wrapper_kwargs: |
94 | + additional keyword arguments passed to each instantiated wrapper |
95 | """ |
96 | self._wrapper = wrapper |
97 | + self._wrapper_args = wrapper_args |
98 | + self._wrapper_kwargs = wrapper_kwargs |
99 | self._plugins = collections.OrderedDict() |
100 | self._loaded = False |
101 | self._mocked_objects = None |
102 | + self._problem_list = [] |
103 | if load: |
104 | self.load() |
105 | |
106 | def get_by_name(self, name): |
107 | """ |
108 | Get the specified plug-in (by name) |
109 | + |
110 | + :param name: |
111 | + name of the plugin to locate |
112 | + :returns: |
113 | + :class:`PlugIn` like object associated with the name |
114 | + :raises KeyError: |
115 | + if the specified name cannot be found |
116 | """ |
117 | return self._plugins[name] |
118 | |
119 | def get_all_names(self): |
120 | """ |
121 | - Get an iterator to a sequence of plug-in names |
122 | + Get a list of all the plug-in names |
123 | + |
124 | + :returns: |
125 | + a list of plugin names |
126 | """ |
127 | return list(self._plugins.keys()) |
128 | |
129 | def get_all_plugins(self): |
130 | """ |
131 | - Get an iterator to a sequence plug-ins |
132 | + Get a list of all the plug-ins |
133 | + |
134 | + :returns: |
135 | + a list of plugin objects |
136 | """ |
137 | return list(self._plugins.values()) |
138 | |
139 | + def get_all_plugin_objects(self): |
140 | + """ |
141 | + Get an list of plug-in objects |
142 | + """ |
143 | + return [plugin.plugin_object for plugin in self._plugins.values()] |
144 | + |
145 | def get_all_items(self): |
146 | """ |
147 | - Get an iterator to a sequence of (name, plug-in) |
148 | + Get a list of all the pairs of plugin name and plugin |
149 | + |
150 | + :returns: |
151 | + a list of tuples (plugin.plugin_name, plugin) |
152 | """ |
153 | return list(self._plugins.items()) |
154 | |
155 | + @property |
156 | + def problem_list(self): |
157 | + """ |
158 | + List of problems encountered while loading plugins |
159 | + """ |
160 | + return self._problem_list |
161 | + |
162 | @contextlib.contextmanager |
163 | - def fake_plugins(self, plugins): |
164 | + def fake_plugins(self, plugins, problem_list=None): |
165 | """ |
166 | Context manager for using fake list of plugins |
167 | |
168 | - :param plugins: list of PlugIn-alike objects |
169 | + :param plugins: |
170 | + list of PlugIn-alike objects |
171 | + :param problem_list: |
172 | + list of problems (exceptions) |
173 | |
174 | The provided list of plugins overrides any previously loaded |
175 | plugins and prevent loading any other, real, plugins. After |
176 | the context manager exits the previous state is restored. |
177 | """ |
178 | old_loaded = self._loaded |
179 | + old_problem_list = self._problem_list |
180 | old_plugins = self._plugins |
181 | self._loaded = True |
182 | self._plugins = collections.OrderedDict([ |
183 | (plugin.plugin_name, plugin) |
184 | for plugin in sorted( |
185 | plugins, key=lambda plugin: plugin.plugin_name)]) |
186 | + if problem_list is None: |
187 | + problem_list = [] |
188 | + self._problem_list = problem_list |
189 | try: |
190 | yield |
191 | finally: |
192 | self._loaded = old_loaded |
193 | self._plugins = old_plugins |
194 | + self._problem_list = old_problem_list |
195 | |
196 | def wrap_and_add_plugin(self, plugin_name, plugin_obj): |
197 | + """ |
198 | + Internal method of PlugInCollectionBase. |
199 | + |
200 | + :param plugin_name: |
201 | + plugin name, some arbitrary string |
202 | + :param plugin_obj: |
203 | + plugin object, some arbitrary object. |
204 | + |
205 | + This method prepares a wrapper (PlugIn subclass instance) for the |
206 | + specified plugin name/object by attempting to instantiate the wrapper |
207 | + class. If a PlugInError exception is raised then it is added to the |
208 | + problem_list and the corresponding plugin is not added to the |
209 | + collection of plugins. |
210 | + """ |
211 | try: |
212 | - wrapper = self._wrapper(plugin_name, plugin_obj) |
213 | + wrapper = self._wrapper( |
214 | + plugin_name, plugin_obj, |
215 | + *self._wrapper_args, **self._wrapper_kwargs) |
216 | except PlugInError as exc: |
217 | logger.warning( |
218 | "Unable to prepare plugin %s: %s", plugin_name, exc) |
219 | + self._problem_list.append(exc) |
220 | else: |
221 | self._plugins[plugin_name] = wrapper |
222 | |
223 | @@ -248,7 +334,8 @@ |
224 | may be adjusted if required. |
225 | """ |
226 | |
227 | - def __init__(self, namespace, load=False, wrapper=PlugIn): |
228 | + def __init__(self, namespace, load=False, wrapper=PlugIn, *wrapper_args, |
229 | + **wrapper_kwargs): |
230 | """ |
231 | Initialize a collection of plug-ins from the specified name-space. |
232 | |
233 | @@ -258,9 +345,13 @@ |
234 | if true, load all of the plug-ins now |
235 | :param wrapper: |
236 | wrapper class for all loaded objects, defaults to :class:`PlugIn` |
237 | + :param wrapper_args: |
238 | + additional arguments passed to each instantiated wrapper |
239 | + :param wrapper_kwargs: |
240 | + additional keyword arguments passed to each instantiated wrapper |
241 | """ |
242 | self._namespace = namespace |
243 | - super(PkgResourcesPlugInCollection, self).__init__(load, wrapper) |
244 | + super().__init__(load, wrapper, *wrapper_args, **wrapper_kwargs) |
245 | |
246 | def load(self): |
247 | """ |
248 | @@ -280,8 +371,9 @@ |
249 | for entry_point in sorted(iterator, key=lambda ep: ep.name): |
250 | try: |
251 | obj = entry_point.load() |
252 | - except ImportError: |
253 | + except ImportError as exc: |
254 | logger.exception("Unable to import %s", entry_point) |
255 | + self._problem_list.append(exc) |
256 | else: |
257 | self.wrap_and_add_plugin(entry_point.name, obj) |
258 | |
259 | @@ -307,7 +399,8 @@ |
260 | each plugin is the text read from the plugin file. |
261 | """ |
262 | |
263 | - def __init__(self, path, ext, load=False, wrapper=PlugIn): |
264 | + def __init__(self, path, ext, load=False, wrapper=PlugIn, *wrapper_args, |
265 | + **wrapper_kwargs): |
266 | """ |
267 | Initialize a collection of plug-ins from the specified name-space. |
268 | |
269 | @@ -316,15 +409,19 @@ |
270 | directory entries. Each directory is searched for (not recursively) |
271 | for plugins. |
272 | :param ext: |
273 | - extension of each plugin definition file |
274 | + extension of each plugin definition file (or a list of those) |
275 | :param load: |
276 | if true, load all of the plug-ins now |
277 | :param wrapper: |
278 | wrapper class for all loaded objects, defaults to :class:`PlugIn` |
279 | + :param wrapper_args: |
280 | + additional arguments passed to each instantiated wrapper |
281 | + :param wrapper_kwargs: |
282 | + additional keyword arguments passed to each instantiated wrapper |
283 | """ |
284 | self._path = path |
285 | self._ext = ext |
286 | - super(FsPlugInCollection, self).__init__(load, wrapper) |
287 | + super().__init__(load, wrapper, *wrapper_args, **wrapper_kwargs) |
288 | |
289 | def load(self): |
290 | """ |
291 | @@ -341,8 +438,9 @@ |
292 | try: |
293 | with open(filename, encoding='UTF-8') as stream: |
294 | text = stream.read() |
295 | - except IOError as exc: |
296 | + except (OSError, IOError) as exc: |
297 | logger.error("Unable to load %r: %s", filename, str(exc)) |
298 | + self._problem_list.append(exc) |
299 | else: |
300 | self.wrap_and_add_plugin(filename, text) |
301 | |
302 | @@ -362,8 +460,15 @@ |
303 | # Look at each file there |
304 | for entry in entries: |
305 | # Skip files with other extensions |
306 | - if not entry.endswith(self._ext): |
307 | - continue |
308 | + if isinstance(self._ext, str): |
309 | + if not entry.endswith(self._ext): |
310 | + continue |
311 | + elif isinstance(self._ext, collections.Sequence): |
312 | + for ext in self._ext: |
313 | + if entry.endswith(ext): |
314 | + break |
315 | + else: |
316 | + continue |
317 | info_file = os.path.join(path, entry) |
318 | # Skip all non-files |
319 | if not os.path.isfile(info_file): |
320 | |
321 | === modified file 'plainbox/plainbox/impl/secure/test_plugins.py' |
322 | --- plainbox/plainbox/impl/secure/test_plugins.py 2013-11-27 20:40:19 +0000 |
323 | +++ plainbox/plainbox/impl/secure/test_plugins.py 2013-12-20 13:53:55 +0000 |
324 | @@ -25,29 +25,254 @@ |
325 | """ |
326 | |
327 | from unittest import TestCase |
328 | +import collections |
329 | import os |
330 | |
331 | from plainbox.impl.secure.plugins import FsPlugInCollection |
332 | from plainbox.impl.secure.plugins import IPlugIn, PlugIn |
333 | from plainbox.impl.secure.plugins import PkgResourcesPlugInCollection |
334 | +from plainbox.impl.secure.plugins import PlugInCollectionBase |
335 | +from plainbox.impl.secure.plugins import PlugInError |
336 | from plainbox.vendor import mock |
337 | |
338 | |
339 | class PlugInTests(TestCase): |
340 | - |
341 | - def test_smoke(self): |
342 | - obj = mock.Mock() |
343 | - # Create a plug-in |
344 | - plugin = PlugIn("name", obj) |
345 | - # Ensure that plugin name and plugin object are okay |
346 | - self.assertEqual(plugin.plugin_name, "name") |
347 | - self.assertEqual(plugin.plugin_object, obj) |
348 | + """ |
349 | + Tests for PlugIn class |
350 | + """ |
351 | + |
352 | + NAME = "name" |
353 | + OBJ = mock.Mock(name="obj") |
354 | + |
355 | + def setUp(self): |
356 | + self.plugin = PlugIn(self.NAME, self.OBJ) |
357 | + |
358 | + def test_property_name(self): |
359 | + """ |
360 | + verify that PlugIn.plugin_name getter works |
361 | + """ |
362 | + self.assertEqual(self.plugin.plugin_name, self.NAME) |
363 | + |
364 | + def test_property_object(self): |
365 | + """ |
366 | + verify that PlugIn.plugin_object getter works |
367 | + """ |
368 | + self.assertEqual(self.plugin.plugin_object, self.OBJ) |
369 | + |
370 | + def test_repr(self): |
371 | + """ |
372 | + verify that repr for PlugIn works |
373 | + """ |
374 | + self.assertEqual(repr(self.plugin), "<PlugIn plugin_name:'name'>") |
375 | |
376 | def test_base_cls(self): |
377 | + """ |
378 | + verify that PlugIn inherits IPlugIn |
379 | + """ |
380 | self.assertTrue(issubclass(PlugIn, IPlugIn)) |
381 | |
382 | |
383 | +class DummyPlugInCollection(PlugInCollectionBase): |
384 | + """ |
385 | + A dummy, concrete subclass of PlugInCollectionBase |
386 | + """ |
387 | + |
388 | + def load(self): |
389 | + """ |
390 | + dummy implementation of load() |
391 | + |
392 | + :raises NotImplementedError: |
393 | + always raised |
394 | + """ |
395 | + raise NotImplementedError("this is a dummy method") |
396 | + |
397 | + |
398 | +class PlugInCollectionBaseTests(TestCase): |
399 | + """ |
400 | + Tests for PlugInCollectionBase class. |
401 | + |
402 | + Since this is an abstract class we're creating a concrete subclass with |
403 | + dummy implementation of the load() method. |
404 | + """ |
405 | + |
406 | + def setUp(self): |
407 | + self.col = DummyPlugInCollection() |
408 | + self.plug1 = PlugIn("name1", "obj1") |
409 | + self.plug2 = PlugIn("name2", "obj2") |
410 | + |
411 | + @mock.patch.object(DummyPlugInCollection, "load") |
412 | + def test_auto_loading(self, mock_col): |
413 | + """ |
414 | + verify that PlugInCollectionBase.load() is called when load=True is |
415 | + passed to the initializer. |
416 | + """ |
417 | + col = DummyPlugInCollection(load=True) |
418 | + col.load.assert_called() |
419 | + |
420 | + def test_defaults(self): |
421 | + """ |
422 | + verify what defaults are passed to the initializer or set internally |
423 | + """ |
424 | + self.assertEqual(self.col._wrapper, PlugIn) |
425 | + self.assertEqual(self.col._plugins, collections.OrderedDict()) |
426 | + self.assertEqual(self.col._loaded, False) |
427 | + self.assertEqual(self.col._mocked_objects, None) |
428 | + self.assertEqual(self.col._problem_list, []) |
429 | + |
430 | + def test_get_by_name__typical(self): |
431 | + """ |
432 | + verify that PlugInCollectionBase.get_by_name() works |
433 | + """ |
434 | + with self.col.fake_plugins([self.plug1]): |
435 | + self.assertEqual( |
436 | + self.col.get_by_name(self.plug1.plugin_name), self.plug1) |
437 | + |
438 | + def test_get_by_name__missing(self): |
439 | + """ |
440 | + check how PlugInCollectionBase.get_by_name() behaves when there is no |
441 | + match for the given name. |
442 | + """ |
443 | + with self.assertRaises(KeyError), self.col.fake_plugins([]): |
444 | + self.col.get_by_name(self.plug1.plugin_name) |
445 | + |
446 | + def test_get_all_names(self): |
447 | + """ |
448 | + verify that PlugInCollectionBase.get_all_names() works |
449 | + """ |
450 | + with self.col.fake_plugins([self.plug1, self.plug2]): |
451 | + self.assertEqual( |
452 | + self.col.get_all_names(), |
453 | + [self.plug1.plugin_name, self.plug2.plugin_name]) |
454 | + |
455 | + def test_get_all_plugins(self): |
456 | + """ |
457 | + verify that PlugInCollectionBase.get_all_plugins() works |
458 | + """ |
459 | + with self.col.fake_plugins([self.plug1, self.plug2]): |
460 | + self.assertEqual( |
461 | + self.col.get_all_plugins(), [self.plug1, self.plug2]) |
462 | + |
463 | + def test_get_all_plugin_objects(self): |
464 | + """ |
465 | + verify that PlugInCollectionBase.get_all_plugin_objects() works |
466 | + """ |
467 | + with self.col.fake_plugins([self.plug1, self.plug2]): |
468 | + self.assertEqual( |
469 | + self.col.get_all_plugin_objects(), |
470 | + [self.plug1.plugin_object, self.plug2.plugin_object]) |
471 | + |
472 | + def test_get_items(self): |
473 | + """ |
474 | + verify that PlugInCollectionBase.get_all_items() works |
475 | + """ |
476 | + with self.col.fake_plugins([self.plug1, self.plug2]): |
477 | + self.assertEqual( |
478 | + self.col.get_all_items(), |
479 | + [(self.plug1.plugin_name, self.plug1), |
480 | + (self.plug2.plugin_name, self.plug2)]) |
481 | + |
482 | + def test_problem_list(self): |
483 | + """ |
484 | + verify that PlugInCollectionBase.problem_list works |
485 | + """ |
486 | + self.assertIs(self.col.problem_list, self.col._problem_list) |
487 | + |
488 | + def test_fake_plugins(self): |
489 | + """ |
490 | + verify that PlugInCollectionBase.fake_plugins() works |
491 | + """ |
492 | + # create a canary object we'll check for below |
493 | + canary = object() |
494 | + # store it to all the attributes we expect to see changed by |
495 | + # fake_plugins() |
496 | + self.col._loaded = canary |
497 | + self.col._plugins = canary |
498 | + self.col._problems = canary |
499 | + # use fake_plugins() with some plugins we have |
500 | + fake_plugins = [self.plug1, self.plug2] |
501 | + with self.col.fake_plugins(fake_plugins): |
502 | + # ensure that we don't have canaries here |
503 | + self.assertEqual(self.col._loaded, True) |
504 | + self.assertEqual(self.col._plugins, collections.OrderedDict([ |
505 | + (self.plug1.plugin_name, self.plug1), |
506 | + (self.plug2.plugin_name, self.plug2)])) |
507 | + self.assertEqual(self.col._problem_list, []) |
508 | + # ensure that we see canaries outside of the context manager |
509 | + self.assertEqual(self.col._loaded, canary) |
510 | + self.assertEqual(self.col._plugins, canary) |
511 | + self.assertEqual(self.col._problems, canary) |
512 | + |
513 | + def test_fake_plugins__with_problem_list(self): |
514 | + """ |
515 | + verify that PlugInCollectionBase.fake_plugins() works when called with |
516 | + the optional problem list. |
517 | + """ |
518 | + # create a canary object we'll check for below |
519 | + canary = object() |
520 | + # store it to all the attributes we expect to see changed by |
521 | + # fake_plugins() |
522 | + self.col._loaded = canary |
523 | + self.col._plugins = canary |
524 | + self.col._problems = canary |
525 | + # use fake_plugins() with some plugins we have |
526 | + fake_plugins = [self.plug1, self.plug2] |
527 | + fake_problems = [PlugInError("just testing")] |
528 | + with self.col.fake_plugins(fake_plugins, fake_problems): |
529 | + # ensure that we don't have canaries here |
530 | + self.assertEqual(self.col._loaded, True) |
531 | + self.assertEqual(self.col._plugins, collections.OrderedDict([ |
532 | + (self.plug1.plugin_name, self.plug1), |
533 | + (self.plug2.plugin_name, self.plug2)])) |
534 | + self.assertEqual(self.col._problem_list, fake_problems) |
535 | + # ensure that we see canaries outside of the context manager |
536 | + self.assertEqual(self.col._loaded, canary) |
537 | + self.assertEqual(self.col._plugins, canary) |
538 | + self.assertEqual(self.col._problems, canary) |
539 | + |
540 | + def test_wrap_and_add_plugin__normal(self): |
541 | + """ |
542 | + verify that PlugInCollectionBase.wrap_and_add_plugin() works |
543 | + """ |
544 | + self.col.wrap_and_add_plugin("new-name", "new-obj") |
545 | + self.assertIn("new-name", self.col._plugins) |
546 | + self.assertEqual( |
547 | + self.col._plugins["new-name"].plugin_name, "new-name") |
548 | + self.assertEqual( |
549 | + self.col._plugins["new-name"].plugin_object, "new-obj") |
550 | + |
551 | + def test_wrap_and_add_plugin__problem(self): |
552 | + """ |
553 | + verify that PlugInCollectionBase.wrap_and_add_plugin() works when a |
554 | + problem occurs. |
555 | + """ |
556 | + with mock.patch.object(self.col, "_wrapper") as mock_wrapper: |
557 | + mock_wrapper.side_effect = PlugInError |
558 | + self.col.wrap_and_add_plugin("new-name", "new-obj") |
559 | + mock_wrapper.assert_called_with("new-name", "new-obj") |
560 | + self.assertIsInstance(self.col.problem_list[0], PlugInError) |
561 | + self.assertNotIn("new-name", self.col._plugins) |
562 | + |
563 | + def test_extra_wrapper_args(self): |
564 | + """ |
565 | + verify that PlugInCollectionBase passes extra arguments to the wrapper |
566 | + """ |
567 | + class TestPlugIn(PlugIn): |
568 | + |
569 | + def __init__(self, name, obj, *args, **kwargs): |
570 | + super().__init__(name, obj) |
571 | + self.args = args |
572 | + self.kwargs = kwargs |
573 | + col = DummyPlugInCollection( |
574 | + False, TestPlugIn, 1, 2, 3, some="argument") |
575 | + col.wrap_and_add_plugin("name", "obj") |
576 | + self.assertEqual(col._plugins["name"].args, (1, 2, 3)) |
577 | + self.assertEqual(col._plugins["name"].kwargs, {"some": "argument"}) |
578 | + |
579 | + |
580 | class PkgResourcesPlugInCollectionTests(TestCase): |
581 | + """ |
582 | + Tests for PlugInCollectionBase class |
583 | + """ |
584 | |
585 | _NAMESPACE = "namespace" |
586 | |
587 | @@ -114,23 +339,8 @@ |
588 | # Ensure that an exception was logged |
589 | mock_logger.exception.assert_called_with( |
590 | "Unable to import %s", mock_ep2) |
591 | - |
592 | - def test_fake_plugins(self): |
593 | - # Create a mocked entry plugin |
594 | - plug1 = PlugIn("ep1", "obj1") |
595 | - plug2 = PlugIn("ep2", "obj2") |
596 | - # With fake plugins |
597 | - with self.col.fake_plugins([plug1, plug2]): |
598 | - # Check that plugins are correct |
599 | - self.assertIs(self.col.get_by_name('ep1'), plug1) |
600 | - self.assertIs(self.col.get_by_name('ep2'), plug2) |
601 | - # Access all plugins |
602 | - self.assertEqual(self.col.get_all_plugins(), [plug1, plug2]) |
603 | - # Access all plugin names |
604 | - self.assertEqual(self.col.get_all_names(), ['ep1', 'ep2']) |
605 | - # Access all pairs (name, plugin) |
606 | - self.assertEqual(self.col.get_all_items(), |
607 | - [('ep1', plug1), ('ep2', plug2)]) |
608 | + # Ensure that the error was collected |
609 | + self.assertIsInstance(self.col.problem_list[0], ImportError) |
610 | |
611 | |
612 | class FsPlugInCollectionTests(TestCase): |
613 | @@ -190,9 +400,8 @@ |
614 | return not os.path.basename(path).startswith('dir.') |
615 | |
616 | def fake_open(path, encoding=None, mode=None): |
617 | - m = mock.Mock() |
618 | - m.__enter__ = mock.Mock() |
619 | - m.__exit__ = mock.Mock() |
620 | + m = mock.MagicMock(name='opened file {!r}'.format(path)) |
621 | + m.__enter__.return_value = m |
622 | if path == os.path.join(self._P1, 'foo.plugin'): |
623 | m.read.return_value = "foo" |
624 | return m |
625 | @@ -200,7 +409,7 @@ |
626 | m.read.return_value = "bar" |
627 | return m |
628 | elif path == os.path.join(self._P1, 'noperm.plugin'): |
629 | - raise IOError("You cannot open this file") |
630 | + raise OSError("You cannot open this file") |
631 | else: |
632 | raise IOError("Unexpected file: {}".format(path)) |
633 | mock_listdir.side_effect = fake_listdir |
634 | @@ -239,3 +448,63 @@ |
635 | 'Unable to load %r: %s', |
636 | '/system/providers/noperm.plugin', |
637 | 'You cannot open this file') |
638 | + # Ensure that all of the errors are collected |
639 | + # Using repr() since OSError seems hard to compare correctly |
640 | + self.assertEqual( |
641 | + repr(self.col.problem_list[0]), |
642 | + repr(OSError('You cannot open this file'))) |
643 | + |
644 | + @mock.patch('plainbox.impl.secure.plugins.logger') |
645 | + @mock.patch('builtins.open') |
646 | + @mock.patch('os.path.isfile') |
647 | + @mock.patch('os.listdir') |
648 | + def test_load__two_extensions(self, mock_listdir, mock_isfile, mock_open, |
649 | + mock_logger): |
650 | + """ |
651 | + verify that FsPlugInCollection works with multiple extensions |
652 | + """ |
653 | + mock_listdir.return_value = ["foo.txt", "bar.txt.in"] |
654 | + mock_isfile.return_value = True |
655 | + |
656 | + def fake_open(path, encoding=None, mode=None): |
657 | + m = mock.MagicMock(name='opened file {!r}'.format(path)) |
658 | + m.read.return_value = "text" |
659 | + m.__enter__.return_value = m |
660 | + return m |
661 | + mock_open.side_effect = fake_open |
662 | + # Create a collection that looks for both extensions |
663 | + col = FsPlugInCollection(self._P1, (".txt", ".txt.in")) |
664 | + # Load everything |
665 | + col.load() |
666 | + # Ensure that we actually tried to look at the filesystem |
667 | + self.assertEqual( |
668 | + mock_listdir.call_args_list, [ |
669 | + ((self._P1, ), {}), |
670 | + ]) |
671 | + # Ensure that we actually tried to check if things are files |
672 | + self.assertEqual( |
673 | + mock_isfile.call_args_list, [ |
674 | + ((os.path.join(self._P1, 'foo.txt'),), {}), |
675 | + ((os.path.join(self._P1, 'bar.txt.in'),), {}), |
676 | + ]) |
677 | + # Ensure that we actually tried to open some files |
678 | + self.assertEqual( |
679 | + mock_open.call_args_list, [ |
680 | + ((os.path.join(self._P1, 'bar.txt.in'),), |
681 | + {'encoding': 'UTF-8'}), |
682 | + ((os.path.join(self._P1, 'foo.txt'),), |
683 | + {'encoding': 'UTF-8'}), |
684 | + ]) |
685 | + # Ensure that no exception was logged |
686 | + mock_logger.error.assert_not_called() |
687 | + # Ensure that everything was okay |
688 | + self.assertEqual(col.problem_list, []) |
689 | + # Ensure that both files got added |
690 | + self.assertEqual( |
691 | + col.get_by_name( |
692 | + os.path.join(self._P1, "foo.txt") |
693 | + ).plugin_object, "text") |
694 | + self.assertEqual( |
695 | + col.get_by_name( |
696 | + os.path.join(self._P1, "bar.txt.in") |
697 | + ).plugin_object, "text") |
I'll work on making sure new features are tested but please do send other comments