[file-roller/wip/jtojnar/dogtail-tests: 1/4] Add basic integration test
- From: Jan Tojnar <jtojnar src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [file-roller/wip/jtojnar/dogtail-tests: 1/4] Add basic integration test
- Date: Thu, 21 Apr 2022 14:35:07 +0000 (UTC)
commit 965503030c475ae203e8537951c15c1a866f329f
Author: Jan Tojnar <jtojnar gmail com>
Date: Tue Apr 19 17:03:43 2022 +0200
Add basic integration test
.gitignore | 2 +
data/meson.build | 11 +++-
default.nix | 6 ++
meson.build | 2 +
meson_options.txt | 6 ++
tests/basic.py | 25 ++++++++
tests/data/texts.tar.gz | Bin 0 -> 220 bytes
tests/meson.build | 37 ++++++++++++
tests/testutil.py | 158 ++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 246 insertions(+), 1 deletion(-)
---
diff --git a/.gitignore b/.gitignore
index cd958352..835002c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,5 @@
.vscode/
build/
po/file-roller.pot
+__pycache__/
+*.pyc
diff --git a/data/meson.build b/data/meson.build
index 54adbd86..0682b1a6 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -1,9 +1,18 @@
po_dir = join_paths(meson.project_source_root(), 'po')
-install_data('org.gnome.FileRoller.gschema.xml',
+settings_schema = 'org.gnome.FileRoller.gschema.xml'
+install_data(
+ settings_schema,
install_dir : join_paths(datadir, 'glib-2.0', 'schemas')
)
+# Required by tests.
+compiled_schemas = gnome.compile_schemas(
+ depend_files: [
+ settings_schema,
+ ],
+)
+
install_data('packages.match',
install_dir : join_paths(datadir, meson.project_name())
)
diff --git a/default.nix b/default.nix
index d3b581ce..83109e2c 100644
--- a/default.nix
+++ b/default.nix
@@ -96,6 +96,12 @@ makeDerivation rec {
meson
ninja
pkg-config
+
+ # For tests
+ python3.pkgs.dogtail
+ python3.pkgs.pygobject3
+ gobject-introspection # for finding typelibs
+
wrapGAppsHook
] ++ lib.optionals shell [
niv
diff --git a/meson.build b/meson.build
index 4bb09ab9..26df4830 100644
--- a/meson.build
+++ b/meson.build
@@ -127,6 +127,7 @@ if build_nautilus_actions
endif
subdir('po')
subdir('src')
+subdir('tests')
gnome.post_install(
gtk_update_icon_cache: true,
@@ -141,6 +142,7 @@ summary = [
'',
' project: @0@ @1@'.format(meson.project_name(), meson.project_version()),
' prefix: @0@'.format(prefix),
+ ' dogtail tests: @0@'.format(enable_dogtail_tests),
' nautilus actions: @0@'.format(build_nautilus_actions),
' packagekit: @0@'.format(get_option('packagekit')),
' libarchive: @0@'.format(use_libarchive),
diff --git a/meson_options.txt b/meson_options.txt
index 6040c516..671e2b2a 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -1,3 +1,9 @@
+option('dogtail',
+ type : 'feature',
+ value : 'disabled',
+ description : 'Enable dogtail-based integration tests',
+)
+
option('run-in-place',
type : 'boolean',
value : false,
diff --git a/tests/basic.py b/tests/basic.py
new file mode 100644
index 00000000..3f88ec23
--- /dev/null
+++ b/tests/basic.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python3
+
+from gi.repository import Gio, GLib
+from pathlib import Path
+from testutil import test_file_roller
+
+with test_file_roller() as (app, app_tree):
+ app.open_file(Path(__file__).parent / "data" / "texts.tar.gz")
+
+ win = app_tree.get_window("texts.tar.gz")
+
+ file_listing = win.child(roleName="table")
+ assert (
+ file_listing.get_n_rows() == 1
+ ), "The archive should contain single directory in top-level"
+
+ texts_directory = file_listing.child("texts")
+ texts_directory.doubleClick()
+
+ assert (
+ file_listing.get_n_rows() == 2
+ ), "The archive should contain two files inside texts/ directory"
+
+ hello_txt = file_listing.child("hello.txt")
+ hello_txt.doubleClick()
diff --git a/tests/data/texts.tar.gz b/tests/data/texts.tar.gz
new file mode 100644
index 00000000..3da452c3
Binary files /dev/null and b/tests/data/texts.tar.gz differ
diff --git a/tests/meson.build b/tests/meson.build
new file mode 100644
index 00000000..e1b6c238
--- /dev/null
+++ b/tests/meson.build
@@ -0,0 +1,37 @@
+pymod = import('python')
+
+python3_for_dogtail_tests = pymod.find_installation(
+ 'python3',
+ modules: [
+ 'dogtail',
+ 'pyatspi',
+ 'gi',
+ 'gi.repository.Gtk',
+ ],
+ required: get_option('dogtail'),
+)
+
+enable_dogtail_tests = python3_for_dogtail_tests.found()
+
+if enable_dogtail_tests
+ test_env = [
+ 'LC_ALL=C',
+ 'G_TEST_SRCDIR=' + meson.current_source_dir(),
+ 'G_TEST_BUILDDIR=' + meson.current_build_dir(),
+ 'GSETTINGS_SCHEMA_DIR=' + meson.project_build_root() / 'data',
+ 'GSETTINGS_BACKEND=memory',
+ ]
+
+ test(
+ 'basic',
+ python3_for_dogtail_tests,
+ args: [
+ meson.current_source_dir() / 'basic.py',
+ ],
+ env: test_env,
+ depends: [
+ compiled_schemas,
+ ],
+ )
+
+endif
diff --git a/tests/testutil.py b/tests/testutil.py
new file mode 100644
index 00000000..0579ab02
--- /dev/null
+++ b/tests/testutil.py
@@ -0,0 +1,158 @@
+from gi.repository import GLib, Gio
+
+from dogtail.utils import isA11yEnabled, enableA11y
+
+if not isA11yEnabled():
+ enableA11y(True)
+
+from contextlib import contextmanager
+from enum import Enum
+from dogtail import tree
+from dogtail.predicate import Predicate
+from pathlib import Path
+from tempfile import TemporaryDirectory
+from textwrap import dedent
+
+import os
+import subprocess
+
+
+class WaitState(Enum):
+ RUNNING = 1 # waiting to see the service
+ SUCCESS = 2 # seen it successfully
+ TIMEOUT = 3 # timed out before seeing it
+
+
+def wait_for_name(bus: Gio.DBusConnection, name: str, timeout: int = 10) -> bool:
+ wait_state = WaitState.RUNNING
+
+ def wait_name_appeared_cb(connection: Gio.DBusConnection, name: str, name_owner: str):
+ nonlocal wait_state
+ wait_state = WaitState.SUCCESS
+
+ def wait_timeout_cb():
+ nonlocal wait_state
+ wait_state = WaitState.TIMEOUT
+
+ watch_id = Gio.bus_watch_name_on_connection(bus, name, Gio.BusNameWatcherFlags.NONE,
wait_name_appeared_cb, None)
+
+ timer_id = GLib.timeout_add_seconds(interval=timeout, function=wait_timeout_cb)
+
+ ctx = GLib.main_context_default()
+ while wait_state == WaitState.RUNNING:
+ ctx.iteration(True)
+
+ Gio.bus_unwatch_name(watch_id)
+ GLib.source_remove(timer_id)
+
+ return wait_state == WaitState.SUCCESS
+
+
+class IsAWindowApproximatelyNamed(Predicate):
+ """Predicate subclass that looks for a top-level window by name substring"""
+
+ def __init__(self, windowNameInfix):
+ self.windowNameInfix = windowNameInfix
+
+ def satisfiedByNode(self, node):
+ return node.roleName == "frame" and self.windowNameInfix in node.name
+
+ def describeSearchResult(self):
+ return f"window with “{self.windowNameInfix}” in name"
+
+
+class FileRollerDbus:
+ APPLICATION_ID = "org.gnome.FileRoller"
+
+ def __init__(self, bus: Gio.DBusConnection):
+ self._bus = bus
+ builddir = os.environ.get("G_TEST_BUILDDIR", None)
+ if builddir and not "TESTUTIL_DONT_START" in os.environ:
+ subprocess.Popen(
+ [os.path.join(builddir, "..", "src", "file-roller")],
+ cwd=os.path.join(builddir, ".."),
+ )
+ else:
+ self._do_bus_call("Activate", GLib.Variant("(a{sv})", ([],)))
+
+ def _do_bus_call(self, method: str, params: GLib.Variant) -> None:
+ self._bus.call_sync(
+ self.APPLICATION_ID,
+ "/" + self.APPLICATION_ID.replace(".", "/"),
+ "org.freedesktop.Application",
+ method,
+ params,
+ None,
+ Gio.DBusCallFlags.NONE,
+ -1,
+ None,
+ )
+
+ def open_file(self, path: Path) -> None:
+ self._do_bus_call(
+ "ActivateAction",
+ GLib.Variant(
+ "(sava{sv})",
+ (
+ "open-archive",
+ [
+ GLib.Variant("s", str(path)),
+ ],
+ [],
+ ),
+ ),
+ )
+
+ def quit(self) -> None:
+ self._do_bus_call(
+ "ActivateAction", GLib.Variant("(sava{sv})", ("quit", [], []))
+ )
+
+
+class FileRollerDogtail:
+ def __init__(self, app_name):
+ self.app_name = app_name
+
+ def get_application(self) -> tree.Application:
+ return tree.root.application(self.app_name)
+
+ def get_window(self, name: str) -> tree.Window:
+ # Cannot use default window finder because the window name has extra space after the file name.
+ return self.get_application().findChild(
+ IsAWindowApproximatelyNamed(name), False
+ )
+
+
+@contextmanager
+def test_file_roller():
+ try:
+ dbus_app = None
+ _bus = Gio.bus_get_sync(Gio.BusType.SESSION)
+ with TemporaryDirectory() as confdir, TemporaryDirectory() as datadir:
+ os.environ["XDG_CONFIG_HOME"] = confdir
+ os.environ["XDG_DATA_HOME"] = datadir
+
+ desktop_file_path = Path(datadir) / "applications" / "dummy-editor.desktop"
+ desktop_file_path.parent.mkdir(parents=True, exist_ok=True)
+ with open(desktop_file_path, "w") as desktop_file:
+ desktop_file.write(
+ dedent(
+ """[Desktop Entry]
+ Exec=sh -c 'exec 3< "$1"; gdbus call --session --dest org.freedesktop.portal.Desktop
--object-path /org/freedesktop/portal/desktop --method org.freedesktop.portal.OpenURI.OpenFile --timeout 5 0
"3" "{}"' portal-open %u
+ MimeType=text/plain;
+ Terminal=false
+ Type=Application
+ """
+ )
+ )
+
+ desktop = Gio.DesktopAppInfo.new_from_filename(str(desktop_file_path))
+ desktop.set_as_default_for_type("text/plain")
+
+ dbus_app = FileRollerDbus(_bus)
+ assert wait_for_name(_bus, dbus_app.APPLICATION_ID), f"Waiting for {dbus_app.APPLICATION_ID}
should not time out."
+ dogtail_app = FileRollerDogtail("file-roller")
+ yield dbus_app, dogtail_app
+ finally:
+ if dbus_app:
+ dbus_app.quit()
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]