summaryrefslogtreecommitdiffstats
path: root/share/extensions/inkex/gui/tester.py
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--share/extensions/inkex/gui/tester.py78
1 files changed, 78 insertions, 0 deletions
diff --git a/share/extensions/inkex/gui/tester.py b/share/extensions/inkex/gui/tester.py
new file mode 100644
index 0000000..c8ce5e7
--- /dev/null
+++ b/share/extensions/inkex/gui/tester.py
@@ -0,0 +1,78 @@
+# coding=utf-8
+#
+# Copyright 2022 Martin Owens <doctormo@geek-2.com>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>
+#
+"""
+Structures for consistant testing of Gtk GUI programs.
+"""
+
+import sys
+from gi.repository import Gtk, GLib
+
+
+class MainLoopProtection:
+ """
+ This protection class provides a way to launch the Gtk mainloop in a test
+ friendly way.
+
+ Exception handling hooks provide a way to see errors that happen
+ inside the main loop, raising them back to the caller.
+ A full timeout in seconds stops the gtk mainloop from operating
+ beyond a set time, acting as a kill switch in the event something
+ has gone horribly wrong.
+
+ Use:
+ with MainLoopProtection(timeout=10s):
+ app.run()
+ """
+
+ def __init__(self, timeout=10):
+ self.timeout = timeout * 1000
+ self._hooked = None
+ self._old_excepthook = None
+
+ def __enter__(self):
+ # replace sys.excepthook with our own and remember hooked raised error
+ self._old_excepthook = sys.excepthook
+ sys.excepthook = self.excepthook
+ # Remove mainloop by force if it doesn't die within 10 seconds
+ self._timeout = GLib.timeout_add(self.timeout, self.idle_exit)
+
+ def __exit__(self, exc, value, traceback): # pragma: no cover
+ """Put the except handler back, cancel the timer and raise if needed"""
+ if self._old_excepthook:
+ sys.excepthook = self._old_excepthook
+ # Remove the timeout, so we don't accidentally kill later mainloops
+ if self._timeout:
+ GLib.source_remove(self._timeout)
+ # Raise an exception if one happened during the test run
+ if self._hooked:
+ exc, value, traceback = self._hooked
+ if value and traceback:
+ raise value.with_traceback(traceback)
+
+ def idle_exit(self): # pragma: no cover
+ """Try to going to kill any running mainloop."""
+ GLib.idle_add(Gtk.main_quit)
+
+ def excepthook(self, ex_type, ex_value, traceback): # pragma: no cover
+ """Catch errors thrown by the Gtk mainloop"""
+ self.idle_exit()
+ # Remember the exception data for raising inside the test context
+ if ex_value is not None:
+ self._hooked = [ex_type, ex_value, traceback]
+ # Fallback and double print the exception (remove if double printing is problematic)
+ return self._old_excepthook(ex_type, ex_value, traceback)