autopkgtest-runner 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+ GNU GENERAL PUBLIC LICENSE
2
+ Version 3, 29 June 2007
3
+
4
+ Copyright (C) 2026 Daniel Nylander <daniel@danielnylander.se>
5
+
6
+ This program is free software: you can redistribute it and/or modify
7
+ it under the terms of the GNU General Public License as published by
8
+ the Free Software Foundation, either version 3 of the License, or
9
+ (at your option) any later version.
10
+
11
+ This program is distributed in the hope that it will be useful,
12
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ GNU General Public License for more details.
15
+
16
+ You should have received a copy of the GNU General Public License
17
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: autopkgtest-runner
3
+ Version: 0.1.4
4
+ Summary: GTK4 frontend for autopkgtest Debian test framework
5
+ Author-email: Daniel Nylander <daniel@danielnylander.se>
6
+ License-Expression: GPL-3.0-or-later
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Dynamic: license-file
11
+
12
+ # Autopkgtest Runner
13
+
14
+ Frontend for the autopkgtest Debian test framework.
15
+
16
+ Built with GTK4/Adwaita. Part of the [Danne L10n Suite](https://github.com/yeager/debian-repo).
17
+
18
+ ## Installation
19
+
20
+ ## Installation from PyPI
21
+
22
+ GTK4, libadwaita and PyGObject must be installed from your distribution (they
23
+ are native system libraries and are not installed by pip). On Debian/Ubuntu:
24
+
25
+ ```bash
26
+ sudo apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1 pipx
27
+ pipx install --system-site-packages autopkgtest-runner
28
+ ```
29
+
30
+
31
+
32
+ ### Debian/Ubuntu
33
+ ```bash
34
+ sudo apt install autopkgtest-runner
35
+ ```
36
+
37
+ ## License
38
+
39
+ GPL-3.0
40
+
41
+ ## Author
42
+
43
+ Daniel Nylander — [danielnylander.se](https://danielnylander.se)
@@ -0,0 +1,32 @@
1
+ # Autopkgtest Runner
2
+
3
+ Frontend for the autopkgtest Debian test framework.
4
+
5
+ Built with GTK4/Adwaita. Part of the [Danne L10n Suite](https://github.com/yeager/debian-repo).
6
+
7
+ ## Installation
8
+
9
+ ## Installation from PyPI
10
+
11
+ GTK4, libadwaita and PyGObject must be installed from your distribution (they
12
+ are native system libraries and are not installed by pip). On Debian/Ubuntu:
13
+
14
+ ```bash
15
+ sudo apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1 pipx
16
+ pipx install --system-site-packages autopkgtest-runner
17
+ ```
18
+
19
+
20
+
21
+ ### Debian/Ubuntu
22
+ ```bash
23
+ sudo apt install autopkgtest-runner
24
+ ```
25
+
26
+ ## License
27
+
28
+ GPL-3.0
29
+
30
+ ## Author
31
+
32
+ Daniel Nylander — [danielnylander.se](https://danielnylander.se)
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ readme = "README.md"
7
+ name = "autopkgtest-runner"
8
+ version = "0.1.4"
9
+ description = "GTK4 frontend for autopkgtest Debian test framework"
10
+ requires-python = ">=3.10"
11
+ license = "GPL-3.0-or-later"
12
+ authors = [{name = "Daniel Nylander", email = "daniel@danielnylander.se"}]
13
+
14
+ [project.scripts]
15
+ autopkgtest-runner = "autopkgtest_runner.main:main"
16
+
17
+ [tool.setuptools.packages.find]
18
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ __version__ = "0.1.4"
@@ -0,0 +1 @@
1
+ from .main import main
@@ -0,0 +1,66 @@
1
+ """Accessibility features: zoom, high contrast, ATK."""
2
+ import gi
3
+ gi.require_version('Gtk', '4.0')
4
+ from gi.repository import Gtk, Gdk, Gio
5
+
6
+
7
+ class AccessibilityManager:
8
+ """Manages accessibility features for a GTK4 window."""
9
+
10
+ def __init__(self, window, app=None):
11
+ self._window = window
12
+ self._app = app or window.get_application()
13
+ self._font_scale = 1.0
14
+ self._high_contrast = False
15
+ self._css = Gtk.CssProvider()
16
+ Gtk.StyleContext.add_provider_for_display(
17
+ Gdk.Display.get_default(), self._css,
18
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + 1
19
+ )
20
+ self._setup_actions()
21
+
22
+ def _setup_actions(self):
23
+ if self._app is None:
24
+ return
25
+ actions = [
26
+ ('zoom-in', self._zoom_in, ['<Control>plus', '<Control>equal']),
27
+ ('zoom-out', self._zoom_out, ['<Control>minus']),
28
+ ('zoom-reset', self._zoom_reset, ['<Control>0']),
29
+ ('toggle-high-contrast', self._toggle_hc, ['<Control><Shift>h']),
30
+ ]
31
+ for name, cb, accels in actions:
32
+ if not self._app.lookup_action(name):
33
+ action = Gio.SimpleAction.new(name, None)
34
+ action.connect('activate', lambda a, p, c=cb: c())
35
+ self._app.add_action(action)
36
+ self._app.set_accels_for_action(f'app.{name}', accels)
37
+
38
+ def _apply_css(self):
39
+ css = f'window {{ font-size: {self._font_scale}em; }}'
40
+ if self._high_contrast:
41
+ css += """
42
+ window.high-contrast {
43
+ border: 2px solid @accent_color;
44
+ font-weight: bold;
45
+ }"""
46
+ self._css.load_from_string(css.encode())
47
+
48
+ def _zoom_in(self):
49
+ self._font_scale = min(self._font_scale + 0.1, 3.0)
50
+ self._apply_css()
51
+
52
+ def _zoom_out(self):
53
+ self._font_scale = max(self._font_scale - 0.1, 0.5)
54
+ self._apply_css()
55
+
56
+ def _zoom_reset(self):
57
+ self._font_scale = 1.0
58
+ self._apply_css()
59
+
60
+ def _toggle_hc(self):
61
+ self._high_contrast = not self._high_contrast
62
+ if self._high_contrast:
63
+ self._window.add_css_class('high-contrast')
64
+ else:
65
+ self._window.remove_css_class('high-contrast')
66
+ self._apply_css()
@@ -0,0 +1,417 @@
1
+ """Autopkgtest Runner — GTK4 frontend for running Debian autopkgtests."""
2
+ import gi
3
+ gi.require_version("Gtk", "4.0")
4
+ gi.require_version("Adw", "1")
5
+ from gi.repository import Gtk, Adw, Gdk, Gio, GLib, Pango
6
+
7
+ import gettext
8
+ import locale
9
+ import os
10
+ import sys
11
+ import json
12
+ import datetime
13
+ import threading
14
+ import subprocess
15
+ import re
16
+ from autopkgtest_runner.accessibility import AccessibilityManager
17
+
18
+ LOCALE_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "po")
19
+ if not os.path.isdir(LOCALE_DIR):
20
+ LOCALE_DIR = "/usr/share/locale"
21
+ gettext.bindtextdomain("autopkgtest-runner", LOCALE_DIR)
22
+ gettext.textdomain("autopkgtest-runner")
23
+ _ = gettext.gettext
24
+
25
+ APP_ID = "se.danielnylander.autopkgtest.runner"
26
+ SETTINGS_DIR = os.path.join(
27
+ os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
28
+ "autopkgtest-runner"
29
+ )
30
+ SETTINGS_FILE = os.path.join(SETTINGS_DIR, "settings.json")
31
+
32
+
33
+ def _load_settings():
34
+ if os.path.exists(SETTINGS_FILE):
35
+ with open(SETTINGS_FILE) as f:
36
+ return json.load(f)
37
+ return {"welcome_shown": False}
38
+
39
+
40
+ def _save_settings(s):
41
+ os.makedirs(SETTINGS_DIR, exist_ok=True)
42
+ with open(SETTINGS_FILE, "w") as f:
43
+ json.dump(s, f, indent=2)
44
+
45
+
46
+
47
+
48
+
49
+ class AutopkgtestRunnerWindow(Adw.ApplicationWindow):
50
+ def __init__(self, app):
51
+ super().__init__(application=app, title=_("Autopkgtest Runner"), default_width=1000, default_height=700)
52
+ self.settings = _load_settings()
53
+
54
+ self._test_results = []
55
+ self._running = False
56
+
57
+ main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
58
+
59
+ # Header
60
+ headerbar = Adw.HeaderBar()
61
+ title_widget = Adw.WindowTitle(title=_("Autopkgtest Runner"), subtitle="")
62
+ headerbar.set_title_widget(title_widget)
63
+ self._title_widget = title_widget
64
+
65
+
66
+ open_btn = Gtk.Button(icon_name="folder-open-symbolic", tooltip_text=_("Open package directory"))
67
+ open_btn.connect("clicked", self._on_open_pkg)
68
+ headerbar.pack_start(open_btn)
69
+
70
+ self._run_btn = Gtk.Button(label=_("Run Tests"))
71
+ self._run_btn.add_css_class("suggested-action")
72
+ self._run_btn.set_sensitive(False)
73
+ self._run_btn.connect("clicked", self._on_run)
74
+ headerbar.pack_end(self._run_btn)
75
+
76
+ # Menu
77
+ menu = Gio.Menu()
78
+ menu.append(_("Settings"), "app.settings")
79
+ menu.append(_("Copy Debug Info"), "app.copy-debug")
80
+ menu.append(_("Keyboard Shortcuts"), "app.shortcuts")
81
+ menu.append(_("About Autopkgtest Runner"), "app.about")
82
+ menu_btn = Gtk.MenuButton(icon_name="open-menu-symbolic", menu_model=menu)
83
+ headerbar.pack_end(menu_btn)
84
+
85
+ main_box.append(headerbar)
86
+
87
+
88
+ # Paned: test list + output
89
+ paned = Gtk.Paned(orientation=Gtk.Orientation.VERTICAL)
90
+ paned.set_vexpand(True)
91
+
92
+ # Top: test list
93
+ top_scroll = Gtk.ScrolledWindow(min_content_height=200)
94
+ self._test_list = Gtk.ListBox()
95
+ self._test_list.set_selection_mode(Gtk.SelectionMode.NONE)
96
+ self._test_list.add_css_class("boxed-list")
97
+ self._test_list.set_margin_start(12)
98
+ self._test_list.set_margin_end(12)
99
+ self._test_list.set_margin_top(8)
100
+ top_scroll.set_child(self._test_list)
101
+ paned.set_start_child(top_scroll)
102
+
103
+ # Bottom: output log
104
+ bottom_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
105
+ out_label = Gtk.Label(label=_("Test Output"), xalign=0)
106
+ out_label.add_css_class("heading")
107
+ out_label.set_margin_start(12)
108
+ out_label.set_margin_top(4)
109
+ bottom_box.append(out_label)
110
+
111
+ out_scroll = Gtk.ScrolledWindow(vexpand=True)
112
+ self._output_view = Gtk.TextView(editable=False, monospace=True)
113
+ self._output_view.set_top_margin(8)
114
+ self._output_view.set_left_margin(8)
115
+ out_scroll.set_child(self._output_view)
116
+ bottom_box.append(out_scroll)
117
+ paned.set_end_child(bottom_box)
118
+ paned.set_position(300)
119
+
120
+ main_box.append(paned)
121
+
122
+ self._pkg_dir = None
123
+
124
+ # Status bar
125
+ self._status = Gtk.Label(label=_("Ready"), xalign=0)
126
+ self._status.set_margin_start(12)
127
+ self._status.set_margin_end(12)
128
+ self._status.set_margin_top(4)
129
+ self._status.set_margin_bottom(4)
130
+ self._status.add_css_class("dim-label")
131
+ main_box.append(self._status)
132
+
133
+ self.set_content(main_box)
134
+
135
+ if not self.settings.get("welcome_shown"):
136
+ GLib.idle_add(self._show_welcome)
137
+
138
+ def _show_welcome(self):
139
+ dialog = Adw.Dialog()
140
+ dialog.set_title(_("Welcome"))
141
+ dialog.set_content_width(420)
142
+ dialog.set_content_height(480)
143
+
144
+ page = Adw.StatusPage()
145
+ page.set_icon_name("system-run-symbolic")
146
+ page.set_title(_("Welcome to Autopkgtest Runner"))
147
+ page.set_description(_("Run Debian autopkgtests easily.\n\n"
148
+ "✓ Run autopkgtest on local packages\n"
149
+ "✓ Visual pass/fail results\n"
150
+ "✓ Live test output streaming\n"
151
+ "✓ Test history and comparison\n"
152
+ "✓ Export test reports"))
153
+
154
+ btn = Gtk.Button(label=_("Get Started"))
155
+ btn.add_css_class("suggested-action")
156
+ btn.add_css_class("pill")
157
+ btn.set_halign(Gtk.Align.CENTER)
158
+ btn.set_margin_top(12)
159
+ btn.connect("clicked", self._on_welcome_close, dialog)
160
+ page.set_child(btn)
161
+
162
+ box = Adw.ToolbarView()
163
+ hb = Adw.HeaderBar()
164
+ hb.set_show_title(False)
165
+ box.add_top_bar(hb)
166
+ box.set_content(page)
167
+ dialog.set_child(box)
168
+ dialog.present(self)
169
+
170
+ def _on_welcome_close(self, btn, dialog):
171
+ self.settings["welcome_shown"] = True
172
+ _save_settings(self.settings)
173
+ dialog.close()
174
+
175
+
176
+ def _on_open_pkg(self, btn):
177
+ dialog = Gtk.FileDialog()
178
+ dialog.set_title(_("Select package directory"))
179
+ dialog.select_folder(self, None, self._on_folder_selected)
180
+
181
+ def _on_folder_selected(self, dialog, result):
182
+ try:
183
+ f = dialog.select_folder_finish(result)
184
+ self._pkg_dir = f.get_path()
185
+ self._run_btn.set_sensitive(True)
186
+ self._title_widget.set_subtitle(os.path.basename(self._pkg_dir))
187
+ # Check for debian/tests
188
+ test_dir = os.path.join(self._pkg_dir, "debian", "tests")
189
+ if os.path.isdir(test_dir):
190
+ control = os.path.join(test_dir, "control")
191
+ if os.path.exists(control):
192
+ with open(control) as fh:
193
+ self._status.set_text(_("Found test control: %s") % control)
194
+ else:
195
+ self._status.set_text(_("No debian/tests/control found"))
196
+ else:
197
+ self._status.set_text(_("No debian/tests directory"))
198
+ except:
199
+ pass
200
+
201
+ def _on_run(self, btn):
202
+ if not self._pkg_dir:
203
+ return
204
+ if self._running:
205
+ self._running = False
206
+ self._run_btn.set_label(_("Run Tests"))
207
+ return
208
+
209
+ self._running = True
210
+ self._run_btn.set_label(_("Stop"))
211
+ self._run_btn.remove_css_class("suggested-action")
212
+ self._run_btn.add_css_class("destructive-action")
213
+ self._output_view.get_buffer().set_text("")
214
+ threading.Thread(target=self._run_tests, daemon=True).start()
215
+
216
+ def _run_tests(self):
217
+ try:
218
+ proc = subprocess.Popen(
219
+ ["autopkgtest", self._pkg_dir, "--", "null"],
220
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
221
+ text=True
222
+ )
223
+ for line in iter(proc.stdout.readline, ""):
224
+ if not self._running:
225
+ proc.terminate()
226
+ break
227
+ GLib.idle_add(self._append_output, line)
228
+ proc.wait()
229
+ GLib.idle_add(self._test_done, proc.returncode)
230
+ except FileNotFoundError:
231
+ GLib.idle_add(self._append_output, _("autopkgtest not installed. Install with: sudo apt install autopkgtest\n"))
232
+ GLib.idle_add(self._test_done, -1)
233
+ except Exception as e:
234
+ GLib.idle_add(self._append_output, str(e) + "\n")
235
+ GLib.idle_add(self._test_done, -1)
236
+
237
+ def _append_output(self, text):
238
+ buf = self._output_view.get_buffer()
239
+ buf.insert(buf.get_end_iter(), text)
240
+
241
+ def _test_done(self, returncode):
242
+ self._running = False
243
+ self._run_btn.set_label(_("Run Tests"))
244
+ self._run_btn.remove_css_class("destructive-action")
245
+ self._run_btn.add_css_class("suggested-action")
246
+
247
+ if returncode == 0:
248
+ icon = "✅"
249
+ status_text = _("All tests passed")
250
+ elif returncode == -1:
251
+ icon = "⚠️"
252
+ status_text = _("Could not run tests")
253
+ else:
254
+ icon = "❌"
255
+ status_text = _("Tests failed (exit code %(code)d)") % {"code": returncode}
256
+
257
+ row = Adw.ActionRow()
258
+ row.set_title(f"{icon} {status_text}")
259
+ row.set_subtitle(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
260
+ self._test_list.prepend(row)
261
+
262
+ ts = datetime.datetime.now().strftime("%H:%M:%S")
263
+ self._status.set_text(_("%(time)s — %(status)s") % {"time": ts, "status": status_text})
264
+
265
+
266
+ class AutopkgtestRunnerApp(Adw.Application):
267
+ def __init__(self):
268
+ super().__init__(application_id=APP_ID, flags=Gio.ApplicationFlags.FLAGS_NONE)
269
+ self.window = None
270
+
271
+ for name, callback in [
272
+ ("settings", self._on_settings),
273
+ ("copy-debug", self._on_copy_debug),
274
+ ("shortcuts", self._on_shortcuts),
275
+ ("about", self._on_about),
276
+ ("quit", self._on_quit),
277
+ ]:
278
+ action = Gio.SimpleAction.new(name, None)
279
+ action.connect("activate", callback)
280
+ self.add_action(action)
281
+
282
+ self.set_accels_for_action("app.quit", ["<Ctrl>q"])
283
+ self.set_accels_for_action("app.shortcuts", ["<Ctrl>slash"])
284
+
285
+ def do_activate(self):
286
+ if not self.window:
287
+ self.window = AutopkgtestRunnerWindow(self)
288
+ self.window.present()
289
+
290
+ def _on_settings(self, *_args):
291
+ if not self.window:
292
+ return
293
+ dialog = Adw.PreferencesDialog()
294
+ dialog.set_title(_("Settings"))
295
+ page = Adw.PreferencesPage()
296
+
297
+ group = Adw.PreferencesGroup(title=_("Test Runner"))
298
+ row = Adw.ComboRow(title=_("Virtualization"))
299
+ row.set_model(Gtk.StringList.new(["null", "schroot", "lxc", "qemu"]))
300
+ group.add(row)
301
+ page.add(group)
302
+ dialog.add(page)
303
+ dialog.present(self.window)
304
+
305
+ def _on_copy_debug(self, *_args):
306
+ if not self.window:
307
+ return
308
+ from . import __version__
309
+ info = (
310
+ f"Autopkgtest Runner {__version__}\n"
311
+ f"Python {sys.version}\n"
312
+ f"GTK {Gtk.MAJOR_VERSION}.{Gtk.MINOR_VERSION}\n"
313
+ f"Adw {Adw.MAJOR_VERSION}.{Adw.MINOR_VERSION}\n"
314
+ f"OS: {os.uname().sysname} {os.uname().release}\n"
315
+ )
316
+ clipboard = Gdk.Display.get_default().get_clipboard()
317
+ clipboard.set(info)
318
+ self.window._status.set_text(_("Debug info copied"))
319
+
320
+ def _on_shortcuts(self, *_args):
321
+ if self.window:
322
+ dialog = Gtk.ShortcutsWindow(transient_for=self.window)
323
+ section = Gtk.ShortcutsSection(visible=True)
324
+ group = Gtk.ShortcutsGroup(title=_("General"), visible=True)
325
+ for accel, title in [
326
+ ("<Ctrl>q", _("Quit")),
327
+ ("<Ctrl>slash", _("Keyboard shortcuts")),
328
+ ]:
329
+ group.append(Gtk.ShortcutsShortcut(accelerator=accel, title=title, visible=True))
330
+ section.append(group)
331
+ dialog.append(section)
332
+ dialog.present()
333
+
334
+ def _on_about(self, *_args):
335
+ from . import __version__
336
+ dialog = Adw.AboutDialog(
337
+ application_name=_("Autopkgtest Runner"),
338
+ application_icon="system-run-symbolic",
339
+ version=__version__,
340
+ developer_name="Daniel Nylander",
341
+ website="https://github.com/yeager/autopkgtest-runner",
342
+ license_type=Gtk.License.GPL_3_0,
343
+ issue_url="https://github.com/yeager/autopkgtest-runner/issues",
344
+ comments=_("Run autopkgtests locally without sbuild. Visual test output with pass/fail indicators."),
345
+ )
346
+ dialog.present(self.window)
347
+
348
+ def _on_quit(self, *_args):
349
+ self.quit()
350
+
351
+
352
+ def main():
353
+ app = AutopkgtestRunnerApp()
354
+ app.run(sys.argv)
355
+
356
+
357
+ # --- Session restore ---
358
+ import json as _json
359
+ import os as _os
360
+
361
+ def _save_session(window, app_name):
362
+ config_dir = _os.path.join(_os.path.expanduser('~'), '.config', app_name)
363
+ _os.makedirs(config_dir, exist_ok=True)
364
+ state = {'width': window.get_width(), 'height': window.get_height(),
365
+ 'maximized': window.is_maximized()}
366
+ try:
367
+ with open(_os.path.join(config_dir, 'session.json'), 'w') as f:
368
+ _json.dump(state, f)
369
+ except OSError:
370
+ pass
371
+
372
+ def _restore_session(window, app_name):
373
+ path = _os.path.join(_os.path.expanduser('~'), '.config', app_name, 'session.json')
374
+ try:
375
+ with open(path) as f:
376
+ state = _json.load(f)
377
+ window.set_default_size(state.get('width', 800), state.get('height', 600))
378
+ if state.get('maximized'):
379
+ window.maximize()
380
+ except (FileNotFoundError, _json.JSONDecodeError, OSError):
381
+ pass
382
+
383
+
384
+ # --- Fullscreen toggle (F11) ---
385
+ def _setup_fullscreen(window, app):
386
+ """Add F11 fullscreen toggle."""
387
+ from gi.repository import Gio
388
+ if not app.lookup_action('toggle-fullscreen'):
389
+ action = Gio.SimpleAction.new('toggle-fullscreen', None)
390
+ action.connect('activate', lambda a, p: (
391
+ window.unfullscreen() if window.is_fullscreen() else window.fullscreen()
392
+ ))
393
+ app.add_action(action)
394
+ app.set_accels_for_action('app.toggle-fullscreen', ['F11'])
395
+
396
+
397
+ # --- Plugin system ---
398
+ import importlib.util
399
+ import os as _pos
400
+
401
+ def _load_plugins(app_name):
402
+ """Load plugins from ~/.config/<app>/plugins/."""
403
+ plugin_dir = _pos.path.join(_pos.path.expanduser('~'), '.config', app_name, 'plugins')
404
+ plugins = []
405
+ if not _pos.path.isdir(plugin_dir):
406
+ return plugins
407
+ for fname in sorted(_pos.listdir(plugin_dir)):
408
+ if fname.endswith('.py') and not fname.startswith('_'):
409
+ path = _pos.path.join(plugin_dir, fname)
410
+ try:
411
+ spec = importlib.util.spec_from_file_location(fname[:-3], path)
412
+ mod = importlib.util.module_from_spec(spec)
413
+ spec.loader.exec_module(mod)
414
+ plugins.append(mod)
415
+ except Exception as e:
416
+ print(f"Plugin {fname}: {e}")
417
+ return plugins
@@ -0,0 +1,43 @@
1
+ Metadata-Version: 2.4
2
+ Name: autopkgtest-runner
3
+ Version: 0.1.4
4
+ Summary: GTK4 frontend for autopkgtest Debian test framework
5
+ Author-email: Daniel Nylander <daniel@danielnylander.se>
6
+ License-Expression: GPL-3.0-or-later
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Dynamic: license-file
11
+
12
+ # Autopkgtest Runner
13
+
14
+ Frontend for the autopkgtest Debian test framework.
15
+
16
+ Built with GTK4/Adwaita. Part of the [Danne L10n Suite](https://github.com/yeager/debian-repo).
17
+
18
+ ## Installation
19
+
20
+ ## Installation from PyPI
21
+
22
+ GTK4, libadwaita and PyGObject must be installed from your distribution (they
23
+ are native system libraries and are not installed by pip). On Debian/Ubuntu:
24
+
25
+ ```bash
26
+ sudo apt install python3-gi gir1.2-gtk-4.0 gir1.2-adw-1 pipx
27
+ pipx install --system-site-packages autopkgtest-runner
28
+ ```
29
+
30
+
31
+
32
+ ### Debian/Ubuntu
33
+ ```bash
34
+ sudo apt install autopkgtest-runner
35
+ ```
36
+
37
+ ## License
38
+
39
+ GPL-3.0
40
+
41
+ ## Author
42
+
43
+ Daniel Nylander — [danielnylander.se](https://danielnylander.se)
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/autopkgtest_runner/__init__.py
5
+ src/autopkgtest_runner/__main__.py
6
+ src/autopkgtest_runner/accessibility.py
7
+ src/autopkgtest_runner/main.py
8
+ src/autopkgtest_runner.egg-info/PKG-INFO
9
+ src/autopkgtest_runner.egg-info/SOURCES.txt
10
+ src/autopkgtest_runner.egg-info/dependency_links.txt
11
+ src/autopkgtest_runner.egg-info/entry_points.txt
12
+ src/autopkgtest_runner.egg-info/top_level.txt
13
+ tests/test_release_sanity.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ autopkgtest-runner = autopkgtest_runner.main:main
@@ -0,0 +1 @@
1
+ autopkgtest_runner
@@ -0,0 +1,25 @@
1
+ import re
2
+ import unittest
3
+ from pathlib import Path
4
+
5
+ ROOT = Path(__file__).resolve().parents[1]
6
+
7
+ class ReleaseSanityTests(unittest.TestCase):
8
+ def test_python_sources_do_not_call_locale_bindtextdomain(self):
9
+ for path in ROOT.rglob("*.py"):
10
+ if ".git" in path.parts or "build" in path.parts or "tests" in path.parts:
11
+ continue
12
+ source = path.read_text(encoding="utf-8")
13
+ self.assertNotIn("locale." + "bindtextdomain(", source, str(path))
14
+
15
+ def test_project_and_package_versions_match(self):
16
+ project = (ROOT / "pyproject.toml").read_text(encoding="utf-8")
17
+ metadata_version = re.search(r'(?m)^version\s*=\s*["\']([^"\']+)', project)
18
+ package_init = ROOT / "src/autopkgtest_runner" / "__init__.py"
19
+ source_version = re.search(r'__version__\s*=\s*["\']([^"\']+)', package_init.read_text(encoding="utf-8"))
20
+ self.assertIsNotNone(metadata_version)
21
+ self.assertIsNotNone(source_version)
22
+ self.assertEqual(metadata_version.group(1), source_version.group(1))
23
+
24
+ if __name__ == "__main__":
25
+ unittest.main()