[Pkg-privacy-commits] [mat] 01/01: add GUI tests

Sascha Steinbiss sascha at steinbiss.name
Sun Jan 24 22:14:20 UTC 2016


This is an automated email from the git hooks/post-receive script.

sascha-guest pushed a commit to branch gui_tests
in repository mat.

commit fade3ad9ff49cb9143931fa44310b187b2307751
Author: Sascha Steinbiss <sascha at steinbiss.name>
Date:   Sun Jan 17 23:55:09 2016 +0000

    add GUI tests
    
    This adds a framework for running GUI autopkgtests using dogtail, Xvfb
    and Python's unittest module. It also adds tests for some criteria
    given in upstream's TESTING file:
    
      - whether the GUI starts up with no messages on stderr
      - whether the GUI actually cleans files (reusing the test file set
        from the commandline version tests)
      - whether unsupported files are handled as intended in cleaned archives
      - whether the status bar displays the messages set in the GUI code
    
    Translations are less straightforward to test for now.
---
 debian/changelog                  |   7 +
 debian/copyright                  |   4 +
 debian/tests/check-mat            | 260 ++++++++++++++++++++++++++++++++++++++
 debian/tests/control              |   4 +
 debian/tests/gui-tests            |  50 ++++++++
 debian/tests/statusbar_watcher.py |  71 +++++++++++
 6 files changed, 396 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index e779240..0fe96b0 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+mat (0.6.1-3) UNRELEASED; urgency=medium
+
+  * Team upload.
+  * Add GUI testsuite to autopkgtest.
+
+ -- Sascha Steinbiss <sascha at steinbiss.name>  Sat, 23 Jan 2016 17:18:44 +0000
+
 mat (0.6.1-2) unstable; urgency=medium
 
   [ Sascha Steinbiss ]
diff --git a/debian/copyright b/debian/copyright
index c81e01a..753ea7f 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -9,6 +9,10 @@ Files: debian/*
 Copyright: © 2011-2015 intrigeri <intrigeri at debian.org>
 License: GPL-2
 
+Files: debian/tests/*
+Copyright: © 2016 Sascha Steinbiss <sascha at steinbiss.name>
+License: GPL-2
+
 Files: libmat/bencode/*
 Copyright: © 2007 Petru Paler <petru at paler.net>
            © 2011 Julien Voisin <julien.voisin at dustri.org>
diff --git a/debian/tests/check-mat b/debian/tests/check-mat
new file mode 100755
index 0000000..8a356f1
--- /dev/null
+++ b/debian/tests/check-mat
@@ -0,0 +1,260 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2016 Sascha Steinbiss <sascha at steinbiss.name>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License, version 2
+# as published by the Free Software Foundation.
+#
+# 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.
+#
+# On Debian GNU/Linux systems, the complete text of version 2 of the
+# General Public License can be found in `/usr/share/common-licenses/GPL-2'.
+
+import gi
+gi.require_version('Atspi', '2.0')
+gi.require_version('Wnck', '3.0')
+import glob
+import os
+import sys
+import shutil
+import time
+import unittest
+import tarfile
+import tempfile
+import subprocess
+import multiprocessing
+from dogtail import rawinput
+from dogtail import tree
+from dogtail.procedural import *
+from dogtail.predicate import GenericPredicate
+from dogtail.utils import run
+from libmat import mat
+from statusbar_watcher import StatusbarWatcher
+
+
+class TestMatGUIFunctional(unittest.TestCase):
+
+    def setUp(self):
+        self.file_list = []
+        self.tmpdir = "run"
+        os.mkdir(self.tmpdir)
+        for dirty_file in glob.glob("dirty/dirty*"):
+            dirty_dir = os.path.join(self.tmpdir, os.path.basename(dirty_file))
+            shutil.copy2(dirty_file, dirty_dir)
+            self.file_list.append(dirty_dir)
+        os.environ["LC_ALL"] = 'C'
+        self.pid = run('mat-gui')
+        self.rootapp = tree.root.application('mat-gui')
+        self.add = self.rootapp.child(roleName="tool bar").child(name="Add")
+        self.clean = self.rootapp.child(
+            roleName="tool bar").child(name="Clean")
+        self.mat_window_title = 'Metadata Anonymisation Toolkit'
+        focus.application("mat-gui")
+        focus.frame(self.mat_window_title)
+
+    def tearDown(self):
+        try:
+            os.kill(self.pid, 0)
+            os.system("kill -9 %i" % self.pid)
+        except OSError:
+            pass
+        for root, dirs, files in os.walk(self.tmpdir):
+            for d in dirs + files:
+                os.chmod(os.path.join(root, d), 0o777)
+        shutil.rmtree(self.tmpdir)
+
+    def make_unsupp_archive(self):
+        """
+        Create an archive with unsupported files.
+        """
+        self.tarpath = os.path.join(self.tmpdir, "test.tar.bz2")
+        tar = tarfile.open(self.tarpath, "w:bz2")
+        for f in glob.glob('*.py'):
+            tar.add(f)
+        tar.close()
+
+    def quit_via_menu(self):
+        """
+        Leave MAT via menu option.
+        """
+        self.rootapp.child(name="File", roleName="menu").click()
+        self.rootapp.child(name="Quit", roleName="menu item").click()
+
+    def test_start_no_warnings(self):
+        """
+        Checks that MAT starts up with no messages on stderr.
+        """
+        self.quit_via_menu()
+        err = ""
+
+        def runner():
+            o = subprocess.Popen("mat-gui",
+                                 stderr=subprocess.PIPE,
+                                 stdout=subprocess.PIPE)
+            lines_iterator = iter(o.stderr.readline, b"")
+            for line in lines_iterator:
+                err += str(err)
+        p = multiprocessing.Process(target=runner)
+        p.start()
+        p.join(10)
+        if p.is_alive():
+            self.assertEquals(len(err), 0)
+            p.terminate()
+            p.join()
+        os.system("killall -9 mat-gui || true")
+
+    def add_dirty_directory_contents(self):
+        """
+        Add the contents of a directory with dirty files to MAT.
+        """
+        focus.frame(self.mat_window_title)
+        self.add.click()
+        window = self.rootapp.child(roleName='file chooser', recursive=False)
+        window.child("mat").click()
+        window.child(self.tmpdir).click()
+        window.child("OK").click()
+        time.sleep(5)
+
+    def test_gui_cleans_stuff_reduced_pdf_quality(self):
+        """
+        Check whether files cleaned via the GUI are actually clean. With PDF
+        option set.
+        """
+        self.rootapp.child("Edit", roleName="menu").click()
+        self.rootapp.child("Preferences", roleName="menu item").click()
+        pref = self.rootapp.child("Preferences", roleName="dialog")
+        pref.child("Reduce PDF quality").click()
+        pref.child("OK").click()
+
+        self.add_dirty_directory_contents()
+        self.clean.click()
+        time.sleep(30)
+
+        self.quit_via_menu()
+
+        for f in self.file_list:
+            current_file = mat.create_class_file(f, False, add2archive=False)
+            self.assertTrue(current_file.is_clean())
+
+    def test_gui_cleans_stuff(self):
+        """
+        Check whether files cleaned via the GUI are actually clean.
+        """
+        self.add_dirty_directory_contents()
+        self.clean.click()
+        time.sleep(30)
+
+        self.quit_via_menu()
+
+        for f in self.file_list:
+            current_file = mat.create_class_file(f, False, add2archive=False)
+            self.assertTrue(current_file.is_clean())
+
+    def add_dirty_archive(self):
+        """
+        Add the contents of an archive wih unsupported files to MAT.
+        """
+        focus.frame(self.mat_window_title)
+        self.add.click()
+        window = self.rootapp.child(roleName='file chooser')
+        window.child("mat").click()
+        window.child(self.tmpdir).click()
+        window.child(self.tmpdir).click()
+        rawinput.typeText('test.tar.bz2')
+        time.sleep(1)
+        rawinput.pressKey('Down')
+        time.sleep(1)
+        rawinput.pressKey('Down')
+        time.sleep(1)
+        rawinput.pressKey('Return')
+        time.sleep(10)
+
+    def _test_gui_archive_with_unsupported_file(self, with_pref_set, select_in_dialog):
+        """
+        Generic runner for unsupported content archive processing.
+        """
+        self.make_unsupp_archive()
+
+        if with_pref_set:
+            self.rootapp.child("Edit", roleName="menu").click()
+            self.rootapp.child("Preferences", roleName="menu item").click()
+            pref = self.rootapp.child("Preferences", roleName="dialog")
+            pref.child("Add unsupported file to archives").click()
+            pref.child("OK").click()
+
+        self.add_dirty_archive()
+        self.clean.click()
+
+        nonsupp = self.rootapp.child("Non-supported files in archive")
+        if select_in_dialog:
+            # click-add all unsupported files in archive
+            children = nonsupp.findChildren(
+                GenericPredicate(roleName='table cell'))
+            for child in children:
+                if child.text is None:
+                    # we don't want to click the filenames, just the checkboxes
+                    child.click()
+        nonsupp.child("Clean", roleName="push button").click()
+
+        self.quit_via_menu()
+
+    def test_gui_archive_with_unsupported_file_select_in_dialog_pref_set(self):
+        self._test_gui_archive_with_unsupported_file(True, True)
+        current_file = mat.create_class_file(
+            self.tarpath, False, add2archive=False)
+        unsupported_files = set(current_file.is_clean(list_unsupported=True))
+        self.assertEqual(unsupported_files, set(glob.glob('*.py')))
+
+    def test_gui_archive_with_unsupported_file_select_in_dialog_pref_not_set(self):
+        self._test_gui_archive_with_unsupported_file(False, True)
+        current_file = mat.create_class_file(
+            self.tarpath, False, add2archive=False)
+        unsupported_files = set(current_file.is_clean(list_unsupported=True))
+        self.assertEqual(unsupported_files, set(glob.glob('*.py')))
+
+    def test_gui_archive_with_unsupported_file_pref_set(self):
+        self._test_gui_archive_with_unsupported_file(True, False)
+        current_file = mat.create_class_file(
+            self.tarpath, False, add2archive=False)
+        unsupported_files = set(current_file.is_clean(list_unsupported=True))
+        self.assertEqual(unsupported_files, set(glob.glob('*.py')))
+
+    def test_gui_archive_with_unsupported_file_pref_not_set(self):
+        self._test_gui_archive_with_unsupported_file(False, False)
+        current_file = mat.create_class_file(
+            self.tarpath, False, add2archive=False)
+        unsupported_files = set(current_file.is_clean(list_unsupported=True))
+        self.assertEqual(unsupported_files, set([]))
+
+    def test_gui_statusbar_updates(self):
+        """
+        Check whether the status bar gets the "Checking/Cleaning X" updates.
+        """
+
+        sw = StatusbarWatcher()
+        sw.start()
+        self.add_dirty_directory_contents()
+        sw.wait_until_last_item_is("Ready")
+        sw.stop()
+        for f in self.file_list:
+            self.assertIn(("Checking %s" % os.path.basename(f)), sw.stbm)
+        self.assertEquals(sw.stbm[-1], "Ready")
+
+        sw = StatusbarWatcher()
+        sw.start()
+        self.clean.click()
+        sw.wait_until_last_item_is("Ready")
+        sw.stop()
+        for f in self.file_list:
+            self.assertIn(("Cleaning %s" % os.path.basename(f)), sw.stbm)
+        self.assertEquals(sw.stbm[-1], "Ready")
+
+        self.quit_via_menu()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/debian/tests/control b/debian/tests/control
index 23f794a..37196cd 100644
--- a/debian/tests/control
+++ b/debian/tests/control
@@ -1,3 +1,7 @@
 Tests: test-installed
 Depends: @
 Restrictions: isolation-container, allow-stderr, needs-recommends
+
+Tests: gui-tests
+Depends: @, psmisc, xvfb, dbus-x11, gir1.2-wnck-3.0, libglib2.0-bin, python-dogtail
+Restrictions: isolation-container, allow-stderr, needs-recommends
diff --git a/debian/tests/gui-tests b/debian/tests/gui-tests
new file mode 100755
index 0000000..131154f
--- /dev/null
+++ b/debian/tests/gui-tests
@@ -0,0 +1,50 @@
+#!/bin/sh
+# autopkgtest check: Run GUI test suite for MAT.
+# Author: Sascha Steinbiss <sascha at steinbiss.name>
+set -e
+
+# set up test directory
+WORKDIR=$(mktemp -d)
+ORIGDIR=$(pwd)
+mkdir -p $WORKDIR/mat/dirty
+mkdir -p $WORKDIR/mat/clean
+cp -p test/dirty* $WORKDIR/mat/dirty
+cp -p test/clean* $WORKDIR/mat/clean
+cp -p test/*py $WORKDIR/mat
+
+# we don't always have a home directory, e.g. on buildds
+export XDG_CONFIG_HOME=$WORKDIR/.config
+export XDG_DATA_HOME=$WORKDIR/.local/share
+export XDG_CACHE_HOME=$WORKDIR/.cache
+
+# start Xvfb
+# note that the screen size must be that large to avoid clickable UI elements
+# going off screen, which would cause dogtail to fail with an error
+(Xvfb :5 -screen 0 1600x1200x24 -ac -noreset -v -fbdir $WORKDIR/ >/dev/null 2>&1 &)
+XVFB_PID=$!
+
+# XXX attach VNC session for local debugging only
+#x11vnc -ncache 10 -display :5 &
+#sleep 2
+#vncviewer localhost &
+
+# finish setting up X
+export DISPLAY=:5
+export XAUTHORITY=/dev/null
+
+# start local D-Bus session
+eval `dbus-launch`
+export DBUS_SESSION_BUS_ADDRESS
+
+# register clean up handler
+trap "rm -rf $WORKDIR && kill $DBUS_SESSION_BUS_PID $XVFB_PID" 0 ABRT TERM QUIT INT
+
+# enable assistive access, required for dogtail
+gsettings set org.gnome.desktop.interface toolkit-accessibility true
+
+# make sure no instance of MAT is running, as it confuses dogtail
+killall -q mat-gui || true
+
+# run test
+cd $WORKDIR/mat
+$ORIGDIR/debian/tests/check-mat
diff --git a/debian/tests/statusbar_watcher.py b/debian/tests/statusbar_watcher.py
new file mode 100644
index 0000000..56d4db6
--- /dev/null
+++ b/debian/tests/statusbar_watcher.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright (C) 2016 Sascha Steinbiss <sascha at steinbiss.name>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License, version 2
+# as published by the Free Software Foundation.
+#
+# 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.
+#
+# On Debian GNU/Linux systems, the complete text of version 2 of the
+# General Public License can be found in `/usr/share/common-licenses/GPL-2'.
+
+import gi
+gi.require_version('Atspi', '2.0')
+gi.require_version('Wnck', '3.0')
+import pyatspi
+import Accessibility
+import re
+import time
+import threading
+
+
+class StatusbarWatcher(object):
+
+    def __init__(self, stbm = None):
+        self.stbm = stbm or []
+        self.running = False
+        self.t = None
+
+        def _runner():
+            def callback(event):
+                if isinstance(event.source, Accessibility.Accessible):
+                    m = re.match(
+                        '\[status bar \| ([^]]+)\]', str(event.source))
+                    if m:
+                        self.stbm.append(m.group(1))
+            self.callback = callback
+            pyatspi.Registry.registerEventListener(
+                self.callback, "object:property-change")
+            pyatspi.Registry.start()
+        self.runner = _runner
+
+    def start(self):
+        if not self.running:
+            self.t = threading.Thread(target=self.runner)
+            try:
+                self.t.start()
+                self.running = True
+            except:
+                self.stop()
+                raise
+
+    def stop(self):
+        pyatspi.Registry.stop()
+        pyatspi.Registry.deregisterEventListener(
+            self.callback, "object:property-change")
+        if self.t:
+            self.t.join()
+        self.running = False
+
+    def wait_until_last_item_is(self, item, timeout=30):
+        i = 0
+        while len(self.stbm) == 0 or self.stbm[-1] != str(item):
+            if i > timeout:
+                break
+            time.sleep(1)
+            i = i + 1

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-privacy/packages/mat.git



More information about the Pkg-privacy-commits mailing list