-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Expand file tree
/
Copy pathtest_plugins.py
More file actions
450 lines (361 loc) · 19.4 KB
/
test_plugins.py
File metadata and controls
450 lines (361 loc) · 19.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# coding:utf-8
from meshroom.core import pluginManager, loadClassesNodes
from meshroom.core.plugins import NodePluginStatus, Plugin
from .utils import overrideOsEnvironmentVariables, registeredPlugins
from pathlib import Path
import os
import time
class TestPluginWithValidNodesOnly:
plugin = None
@classmethod
def setup_class(cls):
folder = os.path.join(os.path.dirname(__file__), "plugins", "meshroom")
package = "pluginA"
cls.plugin = Plugin(package, folder)
nodes = loadClassesNodes(folder, package)
for node in nodes:
cls.plugin.addNodePlugin(node)
pluginManager.addPlugin(cls.plugin)
@classmethod
def teardown_class(cls):
for node in cls.plugin.nodes.values():
pluginManager.unregisterNode(node)
pluginManager.removePlugin(cls.plugin)
cls.plugin = None
def test_loadedPlugin(self):
# Assert that there are loaded plugins, and that "pluginA" is one of them
assert len(pluginManager.getPlugins()) >= 1
plugin = pluginManager.getPlugin("pluginA")
assert plugin == self.plugin
assert str(plugin.path) == os.path.join(os.path.dirname(__file__), "plugins", "meshroom")
# Assert that the nodes of pluginA have been successfully registered
assert len(pluginManager.getRegisteredNodePlugins()) >= 2
for nodeName, nodePlugin in plugin.nodes.items():
assert nodePlugin.status == NodePluginStatus.LOADED
assert pluginManager.isRegistered(nodeName)
# Assert the template has been loaded
assert len(plugin.templates) == 1
name = list(plugin.templates.keys())[0]
assert name == "sharedTemplate"
assert plugin.templates[name] == os.path.join(str(plugin.path), "sharedTemplate.mg")
def test_unloadPlugin(self):
plugin = pluginManager.getPlugin("pluginA")
assert plugin == self.plugin
# Unload the plugin without unregistering the nodes
pluginManager.removePlugin(plugin, unregisterNodePlugins=False)
# Assert the plugin is not loaded anymore
assert pluginManager.getPlugin(plugin.name) is None
# Assert the nodes are still registered and belong to an unloaded plugin
for nodeName, nodePlugin in plugin.nodes.items():
assert nodePlugin.status == NodePluginStatus.LOADED
assert pluginManager.isRegistered(nodeName)
assert pluginManager.belongsToPlugin(nodeName) is None
# Re-add the plugin
pluginManager.addPlugin(plugin, registerNodePlugins=False)
assert pluginManager.getPlugin(plugin.name)
# Unload the plugin with a full unregistration of the nodes
pluginManager.removePlugin(plugin)
# Assert the plugin is not loaded anymore
assert pluginManager.getPlugin(plugin.name) is None
# Assert the nodes have been successfully unregistered
for nodeName, nodePlugin in plugin.nodes.items():
assert nodePlugin.status == NodePluginStatus.NOT_LOADED
assert not pluginManager.isRegistered(nodeName)
# Re-add the plugin and re-register the nodes
pluginManager.addPlugin(plugin)
assert pluginManager.getPlugin(plugin.name)
for nodeName, nodePlugin in plugin.nodes.items():
assert nodePlugin.status == NodePluginStatus.LOADED
assert pluginManager.isRegistered(nodeName)
def test_updateRegisteredNodes(self):
nbRegisteredNodes = len(pluginManager.getRegisteredNodePlugins())
plugin = pluginManager.getPlugin("pluginA")
assert plugin == self.plugin
nodeA = pluginManager.getRegisteredNodePlugin("PluginANodeA")
nodeAName = nodeA.nodeDescriptor.__name__
# Unregister a node
assert nodeA
pluginManager.unregisterNode(nodeA)
# Check that the node has been fully unregistered:
# - its status is "NOT_LOADED"
# - it is still part of pluginA
# - it is not in the list of registered plugins anymore (and returns None when requested)
assert nodeA.status == NodePluginStatus.NOT_LOADED
assert plugin.containsNodePlugin(nodeAName)
assert nodeA.plugin == plugin
assert pluginManager.getRegisteredNodePlugin(nodeAName) is None
assert nodeAName not in pluginManager.getRegisteredNodePlugins()
assert len(pluginManager.getRegisteredNodePlugins()) == nbRegisteredNodes - 1
# Re-register the node
pluginManager.registerNode(nodeA)
assert nodeA.status == NodePluginStatus.LOADED
assert pluginManager.getRegisteredNodePlugin(nodeAName)
assert len(pluginManager.getRegisteredNodePlugins()) == nbRegisteredNodes
class TestPluginWithInvalidNodes:
plugin = None
@classmethod
def setup_class(cls):
folder = os.path.join(os.path.dirname(__file__), "plugins", "meshroom")
package = "pluginB"
cls.plugin = Plugin(package, folder)
nodes = loadClassesNodes(folder, package)
for node in nodes:
cls.plugin.addNodePlugin(node)
pluginManager.addPlugin(cls.plugin)
@classmethod
def teardown_class(cls):
for node in cls.plugin.nodes.values():
pluginManager.unregisterNode(node)
pluginManager.removePlugin(cls.plugin)
cls.plugin = None
def test_loadedPlugin(self):
# Assert that there are loaded plugins, and that "pluginB" is one of them
assert len(pluginManager.getPlugins()) >= 1
plugin = pluginManager.getPlugin("pluginB")
assert plugin == self.plugin
assert str(plugin.path) == os.path.join(os.path.dirname(__file__), "plugins", "meshroom")
# Assert that PluginBNodeA is successfully registered
assert pluginManager.isRegistered("PluginBNodeA")
assert plugin.nodes["PluginBNodeA"].status == NodePluginStatus.LOADED
assert plugin.nodes["PluginBNodeA"].plugin == plugin
# Assert that PluginBNodeB has not been registered (description error)
assert not pluginManager.isRegistered("PluginBNodeB")
assert plugin.nodes["PluginBNodeB"].status == NodePluginStatus.DESC_ERROR
assert plugin.nodes["PluginBNodeB"].plugin == plugin
# Assert the template has been loaded
assert len(plugin.templates) == 1
name = list(plugin.templates.keys())[0]
assert name == "sharedTemplate"
assert plugin.templates[name] == os.path.join(str(plugin.path), "sharedTemplate.mg")
def test_reloadNodePluginInvalidDescrpition(self):
plugin = pluginManager.getPlugin("pluginB")
assert plugin == self.plugin
node = plugin.nodes["PluginBNodeB"]
nodeName = node.nodeDescriptor.__name__
# Check that the node has not been registered
assert node.status == NodePluginStatus.DESC_ERROR
assert not pluginManager.isRegistered(nodeName)
# Check that the node cannot be registered
pluginManager.registerNode(node)
assert not pluginManager.isRegistered(nodeName)
# Replace directly in the node file the line that fails the validation
# on the description with a line that will pass
originalFileContent = None
with open(node.path, "r") as f:
originalFileContent = f.read()
replaceFileContent = originalFileContent.replace('"not an integer"', '1')
with open(node.path, "w") as f:
f.write(replaceFileContent)
# Reload the node and assert it is valid
node.reload()
assert node.status == NodePluginStatus.NOT_LOADED
# Attempt to register node plugin
pluginManager.registerNode(node)
assert pluginManager.isRegistered(nodeName)
# Reload the node again without any change
node.reload()
assert pluginManager.isRegistered(nodeName)
# Hack to ensure that the timestamp of the file will be different after being rewritten
# Without it, on some systems, the operation is too fast and the timestamp does not change,
# cause the test to fail
time.sleep(0.1)
# Restore the node file to its original state (with a description error)
with open(node.path, "w") as f:
f.write(originalFileContent)
timestampOr2 = os.path.getmtime(node.path)
print(f"New timestamp: {timestampOr2}")
print(os.stat(node.path))
# Reload the node and assert it is invalid while still registered
node.reload()
assert node.status == NodePluginStatus.DESC_ERROR
assert pluginManager.isRegistered(nodeName)
# Unregister it
pluginManager.unregisterNode(node)
assert node.status == NodePluginStatus.DESC_ERROR # Not NOT_LOADED
assert not pluginManager.isRegistered(nodeName)
def test_reloadNodePluginSyntaxError(self):
plugin = pluginManager.getPlugin("pluginB")
assert plugin == self.plugin
node = plugin.nodes["PluginBNodeA"]
nodeName = node.nodeDescriptor.__name__
# Check that the node has been registered
assert node.status == NodePluginStatus.LOADED
assert pluginManager.isRegistered(nodeName)
# Introduce a syntax error in the description
originalFileContent = None
with open(node.path, "r") as f:
originalFileContent = f.read()
replaceFileContent = originalFileContent.replace('name="input",', 'name="input"')
with open(node.path, "w") as f:
f.write(replaceFileContent)
# Reload the node and assert it is invalid but still registered
node.reload()
assert node.status == NodePluginStatus.DESC_ERROR
assert pluginManager.isRegistered(nodeName)
# Restore the node file to its original state (with a description error)
with open(node.path, "w") as f:
f.write(originalFileContent)
# Assert the status is correct and the node is still registered
node.reload()
assert node.status == NodePluginStatus.NOT_LOADED
assert pluginManager.isRegistered(nodeName)
class TestPluginsConfiguration:
CONFIG_PATH = ("CONFIG_PATH", "sharedTemplate.mg", "config.json")
ERRONEOUS_CONFIG_PATH = ("ERRONEOUS_CONFIG_PATH", "erroneous_path", "not_erroneous_path")
CONFIG_STRING = ("CONFIG_STRING", "configFile", "notConfigFile")
CONFIG_KEYS = [CONFIG_PATH[0], ERRONEOUS_CONFIG_PATH[0], CONFIG_STRING[0]]
def test_loadedConfig(self):
# Check that the config.json file for the plugins in the "plugins" directory is
# correctly loaded
folder = os.path.join(os.path.dirname(__file__), "plugins")
with registeredPlugins(folder):
plugin = pluginManager.getPlugin("pluginA")
assert plugin
# Check that the config file has been properly loaded
config = plugin.configEnv
configFullEnv = plugin.configFullEnv
assert len(config) == 3, "The configuration file contains exactly 3 keys."
assert len(configFullEnv) >= len(os.environ) and \
len(configFullEnv) == len(os.environ) + len(config), \
"The configuration environment should have the same number of keys as " \
"os.environ and the configuration file"
# Check that all the keys have been properly read
assert list(config.keys()) == self.CONFIG_KEYS
# Check that the valid path has been correctly read, resolved and set
assert configFullEnv[self.CONFIG_PATH[0]] == config[self.CONFIG_PATH[0]]
assert configFullEnv[self.CONFIG_PATH[0]] == Path(
os.path.join(plugin.path, self.CONFIG_PATH[1])).resolve().as_posix()
# Check that the invalid path has been read, unresolved, and set
assert configFullEnv[self.ERRONEOUS_CONFIG_PATH[0]] == self.ERRONEOUS_CONFIG_PATH[1]
assert config[self.ERRONEOUS_CONFIG_PATH[0]] == self.ERRONEOUS_CONFIG_PATH[1]
# Check that the string has been correctly read and set
assert configFullEnv[self.CONFIG_STRING[0]] == self.CONFIG_STRING[1]
assert config[self.CONFIG_STRING[0]] == self.CONFIG_STRING[1]
def test_loadedConfigWithOnlyExistingKeys(self):
# Set the keys from the config file in the current environment
environment = {
self.CONFIG_PATH[0]: self.CONFIG_PATH[2],
self.ERRONEOUS_CONFIG_PATH[0]: self.ERRONEOUS_CONFIG_PATH[2],
self.CONFIG_STRING[0]: self.CONFIG_STRING[2]
}
folder = os.path.join(os.path.dirname(__file__), "plugins")
with (overrideOsEnvironmentVariables(environment), registeredPlugins(folder)):
plugin = pluginManager.getPlugin("pluginA")
assert plugin
# Check that the config file has been properly loaded and read
# Environment variables that are already set should not have any effect on that
# reading of values
config = plugin.configEnv
assert len(config) == 3
assert list(config.keys()) == self.CONFIG_KEYS
assert config[self.CONFIG_PATH[0]] == Path(
os.path.join(plugin.path, self.CONFIG_PATH[1])).resolve().as_posix()
assert config[self.ERRONEOUS_CONFIG_PATH[0]] == self.ERRONEOUS_CONFIG_PATH[1]
assert config[self.CONFIG_STRING[0]] == self.CONFIG_STRING[1]
# Check that the values of the configuration file are not taking precedence over
# those in the environment
configFullEnv = plugin.configFullEnv
assert all(key in configFullEnv for key in config.keys())
assert config[self.CONFIG_PATH[0]] != self.CONFIG_PATH[2]
assert configFullEnv[self.CONFIG_PATH[0]] == self.CONFIG_PATH[2]
assert config[self.ERRONEOUS_CONFIG_PATH[0]] != self.ERRONEOUS_CONFIG_PATH[2]
assert configFullEnv[self.ERRONEOUS_CONFIG_PATH[0]] == self.ERRONEOUS_CONFIG_PATH[2]
assert config[self.CONFIG_STRING[0]] != self.CONFIG_STRING[2]
assert configFullEnv[self.CONFIG_STRING[0]] == self.CONFIG_STRING[2]
def test_loadedConfigWithSomeExistingKeys(self):
# Set some keys from the config file in the current environment
environment = {
self.ERRONEOUS_CONFIG_PATH[0]: self.ERRONEOUS_CONFIG_PATH[2],
self.CONFIG_STRING[0]: self.CONFIG_STRING[2]
}
folder = os.path.join(os.path.dirname(__file__), "plugins")
with (overrideOsEnvironmentVariables(environment), registeredPlugins(folder)):
plugin = pluginManager.getPlugin("pluginA")
assert plugin
# Check that the config file has been properly loaded and read
# Environment variables that are already set should not have any effect on that
# reading of values
config = plugin.configEnv
assert len(config) == 3
assert list(config.keys()) == self.CONFIG_KEYS
assert config[self.CONFIG_PATH[0]] == Path(
os.path.join(plugin.path, self.CONFIG_PATH[1])).resolve().as_posix()
assert config[self.ERRONEOUS_CONFIG_PATH[0]] == self.ERRONEOUS_CONFIG_PATH[1]
assert config[self.CONFIG_STRING[0]] == self.CONFIG_STRING[1]
# Check that the values of the configuration file are not taking precedence over
# those in the environment
configFullEnv = plugin.configFullEnv
assert all(key in configFullEnv for key in config.keys())
assert config[self.CONFIG_PATH[0]] == Path(os.path.join(
plugin.path, self.CONFIG_PATH[1])).resolve().as_posix()
assert configFullEnv[self.CONFIG_PATH[0]] == Path(os.path.join(
plugin.path, self.CONFIG_PATH[1])).resolve().as_posix()
assert config[self.ERRONEOUS_CONFIG_PATH[0]] != self.ERRONEOUS_CONFIG_PATH[2]
assert configFullEnv[self.ERRONEOUS_CONFIG_PATH[0]] == self.ERRONEOUS_CONFIG_PATH[2]
assert config[self.CONFIG_STRING[0]] != self.CONFIG_STRING[2]
assert configFullEnv[self.CONFIG_STRING[0]] == self.CONFIG_STRING[2]
class TestPluginMenuActions:
"""Test the registration and execution of plugin menu actions."""
def test_menuActionsRegistered(self):
"""Check that menu actions registered via registerMenuAction are loaded for the plugin."""
folder = os.path.join(os.path.dirname(__file__), "plugins")
with registeredPlugins(folder):
plugin = pluginManager.getPlugin("pluginA")
assert plugin
actions = plugin.menuActions
assert len(actions) == 2
assert actions[0]["label"] == "Plugin Documentation"
assert actions[0]["tooltip"] == "Open plugin documentation"
assert callable(actions[0]["function"])
assert "actionId" in actions[0]
assert actions[1]["label"] == "Report Issue"
assert actions[1]["tooltip"] == ""
assert callable(actions[1]["function"])
assert "actionId" in actions[1]
def test_getAllMenuActionsAggregation(self):
"""Check that getAllMenuActions aggregates actions from all plugins."""
folder = os.path.join(os.path.dirname(__file__), "plugins")
with registeredPlugins(folder):
allActions = pluginManager.getAllMenuActions()
# pluginA has 2 registered actions; pluginB has none
pluginAActions = [a for a in allActions if a["pluginName"] == "pluginA"]
assert len(pluginAActions) == 2
assert all("label" in a and "actionId" in a and "tooltip" in a for a in pluginAActions)
assert all(a["pluginName"] == "pluginA" for a in pluginAActions)
# The function callable must not be exposed through getAllMenuActions
assert all("function" not in a for a in pluginAActions)
def test_executeMenuAction(self):
"""Check that executeMenuAction calls the registered function."""
folder = os.path.join(os.path.dirname(__file__), "plugins")
with registeredPlugins(folder):
plugin = pluginManager.getPlugin("pluginA")
assert plugin
called = []
def my_action():
called.append(True)
plugin.addMenuAction("Test Action", my_action)
action = next(a for a in plugin.menuActions if a["label"] == "Test Action")
pluginManager.executeMenuAction(action["actionId"])
assert called == [True]
def test_noMenuActionsForNewPlugin(self):
"""Check that a freshly created plugin has no menu actions."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
plugin_no_menu = Plugin("noMenuPlugin", tmpdir)
assert plugin_no_menu.menuActions == []
def test_addMenuActionSkipsEmptyLabel(self):
"""Check that addMenuAction skips entries without a valid label."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
plugin = Plugin("testPlugin", tmpdir)
plugin.addMenuAction("", lambda: None)
plugin.addMenuAction(" ", lambda: None)
assert plugin.menuActions == []
def test_addMenuActionSkipsNonCallableFunction(self):
"""Check that addMenuAction skips entries where function is not callable."""
import tempfile
with tempfile.TemporaryDirectory() as tmpdir:
plugin = Plugin("testPlugin", tmpdir)
plugin.addMenuAction("My Action", "not_a_function")
plugin.addMenuAction("My Action", 42)
assert plugin.menuActions == []