qubes-core-admin-linux/qubesappmenus/tests.py

384 lines
15 KiB
Python
Raw Normal View History

#!/usr/bin/python2
# coding=utf-8
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2016 Marek Marczykowski-Górecki
# <marmarek@invisiblethingslab.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 2
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.
import colorsys
import os
import unittest
import pkg_resources
import xdg
import xdg.BaseDirectory
import xdg.DesktopEntry
import qubes
import qubes.tests
import qubes.tests.extra
import qubes.vm.appvm
import qubes.vm.templatevm
import qubesappmenus
import qubesappmenus.receive
import qubesimgconverter
class TestApp(object):
labels = {1: qubes.Label(1, '0xcc0000', 'red')}
def __init__(self):
self.domains = {}
class TestVM(object):
# pylint: disable=too-few-public-methods
app = TestApp()
def __init__(self, name, **kwargs):
self.running = False
self.installed_by_rpm = False
self.is_template = False
self.name = name
for k, v in kwargs.items():
setattr(self, k, v)
def is_running(self):
return self.running
class TC_00_Appmenus(qubes.tests.QubesTestCase):
"""Unittests for appmenus, theoretically runnable from git checkout"""
def setUp(self):
super(TC_00_Appmenus, self).setUp()
vmname = qubes.tests.VMPREFIX + 'standalone'
self.standalone = TestVM(
name=vmname,
dir_path=os.path.join(qubes.config.qubes_base_dir, 'appvms',
vmname),
updateable=True,
)
vmname = qubes.tests.VMPREFIX + 'template'
self.template = TestVM(
name=vmname,
dir_path=os.path.join(
qubes.config.qubes_base_dir,
'vm-templates', vmname),
is_template=True,
updateable=True,
)
vmname = qubes.tests.VMPREFIX + 'vm'
self.appvm = TestVM(
name=vmname,
dir_path=os.path.join(
qubes.config.qubes_base_dir,
'appvms', vmname),
template=self.template,
updateable=False,
)
self.app = TestApp()
self.ext = qubesappmenus.AppmenusExtension()
def test_000_templates_dir(self):
self.assertEquals(
self.ext.templates_dir(self.standalone),
os.path.join(qubes.config.qubes_base_dir, 'appvms',
self.standalone.name, 'apps.templates')
)
self.assertEquals(
self.ext.templates_dir(self.template),
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
self.template.name, 'apps.templates')
)
self.assertEquals(
self.ext.templates_dir(self.appvm),
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
self.template.name, 'apps.templates')
)
def test_001_template_icons_dir(self):
self.assertEquals(
self.ext.template_icons_dir(self.standalone),
os.path.join(qubes.config.qubes_base_dir, 'appvms',
self.standalone.name, 'apps.tempicons')
)
self.assertEquals(
self.ext.template_icons_dir(self.template),
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
self.template.name, 'apps.tempicons')
)
self.assertEquals(
self.ext.template_icons_dir(self.appvm),
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
self.template.name, 'apps.tempicons')
)
def test_002_appmenus_dir(self):
self.assertEquals(
self.ext.appmenus_dir(self.standalone),
os.path.join(qubes.config.qubes_base_dir, 'appvms',
self.standalone.name, 'apps')
)
self.assertEquals(
self.ext.appmenus_dir(self.template),
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
self.template.name, 'apps')
)
self.assertEquals(
self.ext.appmenus_dir(self.appvm),
os.path.join(qubes.config.qubes_base_dir, 'appvms',
self.appvm.name, 'apps')
)
def test_003_icons_dir(self):
self.assertEquals(
self.ext.icons_dir(self.standalone),
os.path.join(qubes.config.qubes_base_dir, 'appvms',
self.standalone.name, 'apps.icons')
)
self.assertEquals(
self.ext.icons_dir(self.template),
os.path.join(qubes.config.qubes_base_dir, 'vm-templates',
self.template.name, 'apps.icons')
)
self.assertEquals(
self.ext.icons_dir(self.appvm),
os.path.join(qubes.config.qubes_base_dir, 'appvms',
self.appvm.name, 'apps.icons')
)
def test_100_get_appmenus(self):
def _run(cmd, **kwargs):
class PopenMockup(object):
pass
self.assertEquals(cmd, 'QUBESRPC qubes.GetAppmenus dom0')
self.assertEquals(kwargs.get('passio_popen', False), True)
self.assertEquals(kwargs.get('gui', True), False)
p = PopenMockup()
p.stdout = pkg_resources.resource_stream(__name__,
'test-data/appmenus.input')
p.wait = lambda: None
p.returncode = 0
return p
vm = TestVM('test-vm', run=_run)
appmenus = qubesappmenus.receive.get_appmenus(vm)
expected_appmenus = {
'org.gnome.Nautilus.desktop': {
'Name': 'Files',
'Comment': 'Access and organize files',
'Categories': 'GNOME;GTK;Utility;Core;FileManager;',
'Exec': 'qubes-desktop-run '
'/usr/share/applications/org.gnome.Nautilus.desktop',
'Icon': 'system-file-manager',
},
'org.gnome.Weather.Application.desktop': {
'Name': 'Weather',
'Comment': 'Show weather conditions and forecast',
'Categories': 'GNOME;GTK;Utility;Core;',
'Exec': 'qubes-desktop-run '
'/usr/share/applications/org.gnome.Weather.Application.desktop',
'Icon': 'org.gnome.Weather.Application',
},
'org.gnome.Cheese.desktop': {
'Name': 'Cheese',
'GenericName': 'Webcam Booth',
'Comment': 'Take photos and videos with your webcam, with fun graphical effects',
'Categories': 'GNOME;AudioVideo;Video;Recorder;',
'Exec': 'qubes-desktop-run '
'/usr/share/applications/org.gnome.Cheese.desktop',
'Icon': 'cheese',
},
'evince.desktop': {
'Name': 'Document Viewer',
'Comment': 'View multi-page documents',
'Categories': 'GNOME;GTK;Office;Viewer;Graphics;2DGraphics;VectorGraphics;',
'Exec': 'qubes-desktop-run '
'/usr/share/applications/evince.desktop',
'Icon': 'evince',
},
}
self.assertEquals(expected_appmenus, appmenus)
class TC_10_AppmenusIntegration(qubes.tests.extra.ExtraTestCase):
def setUp(self):
super(TC_10_AppmenusIntegration, self).setUp()
self.vm = self.create_vms(['vm'])[0]
self.appmenus = qubesappmenus.AppmenusExtension()
def assertPathExists(self, path):
if not os.path.exists(path):
self.fail("Path {} does not exist".format(path))
def assertPathNotExists(self, path):
if os.path.exists(path):
self.fail("Path {} exists while it should not".format(path))
def get_whitelist(self, whitelist_path):
self.assertPathExists(whitelist_path)
with open(whitelist_path) as f:
whitelisted = [x.rstrip() for x in f.readlines()]
return whitelisted
def test_000_created(self, vm=None):
if vm is None:
vm = self.vm
whitelist_path = os.path.join(vm.dir_path,
qubesappmenus.AppmenusSubdirs.whitelist)
whitelisted = self.get_whitelist(whitelist_path)
self.assertPathExists(self.appmenus.appmenus_dir(vm))
appmenus = os.listdir(self.appmenus.appmenus_dir(vm))
self.assertTrue(all(x.startswith(vm.name + '-') for x in appmenus))
appmenus = [x[len(vm.name) + 1:] for x in appmenus]
self.assertIn('vm.directory', appmenus)
appmenus.remove('vm.directory')
self.assertIn('qubes-appmenu-select.desktop', appmenus)
appmenus.remove('qubes-appmenu-select.desktop')
self.assertEquals(set(whitelisted), set(appmenus))
self.assertPathExists(self.appmenus.icons_dir(vm))
appicons = os.listdir(self.appmenus.icons_dir(vm))
whitelisted_icons = set()
for appmenu in whitelisted:
desktop = xdg.DesktopEntry.DesktopEntry(
os.path.join(self.appmenus.appmenus_dir(vm),
'-'.join((vm.name, appmenu))))
if desktop.getIcon():
whitelisted_icons.add(os.path.basename(desktop.getIcon()))
self.assertEquals(set(whitelisted_icons), set(appicons))
def test_001_created_registered(self):
"""Check whether appmenus was registered in desktop environment"""
whitelist_path = os.path.join(self.vm.dir_path,
qubesappmenus.AppmenusSubdirs.whitelist)
if not os.path.exists(whitelist_path):
self.skipTest("Appmenus whitelist does not exists")
whitelisted = self.get_whitelist(whitelist_path)
for appmenu in whitelisted:
if appmenu.endswith('.directory'):
subdir = 'desktop-directories'
else:
subdir = 'applications'
self.assertPathExists(os.path.join(
xdg.BaseDirectory.xdg_data_home, subdir,
'-'.join([self.vm.name, appmenu])))
# TODO: some KDE specific dir?
def test_002_unregistered_after_remove(self):
"""Check whether appmenus was unregistered after VM removal"""
whitelist_path = os.path.join(self.vm.dir_path,
qubesappmenus.AppmenusSubdirs.whitelist)
if not os.path.exists(whitelist_path):
self.skipTest("Appmenus whitelist does not exists")
whitelisted = self.get_whitelist(whitelist_path)
self.vm.remove_from_disk()
for appmenu in whitelisted:
if appmenu.endswith('.directory'):
subdir = 'desktop-directories'
else:
subdir = 'applications'
self.assertPathNotExists(os.path.join(
xdg.BaseDirectory.xdg_data_home, subdir,
'-'.join([self.vm.name, appmenu])))
def test_003_created_template_empty(self):
tpl = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM,
name=self.make_vm_name('tpl'), label='red')
tpl.create_on_disk()
self.assertPathExists(self.appmenus.templates_dir(tpl))
self.assertPathExists(self.appmenus.template_icons_dir(tpl))
def test_004_created_template_from_other(self):
tpl = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM,
name=self.make_vm_name('tpl'), label='red')
tpl.create_on_disk(source_template=self.app.default_template)
self.assertPathExists(self.appmenus.templates_dir(tpl))
self.assertPathExists(self.appmenus.template_icons_dir(tpl))
self.assertPathExists(os.path.join(tpl.dir_path,
qubesappmenus.AppmenusSubdirs.whitelist))
for appmenu in os.listdir(self.appmenus.templates_dir(
self.app.default_template)):
self.assertPathExists(os.path.join(
self.appmenus.templates_dir(tpl), appmenu))
for appicon in os.listdir(self.appmenus.template_icons_dir(
self.app.default_template)):
self.assertPathExists(os.path.join(
self.appmenus.template_icons_dir(tpl), appicon))
def get_image_color(self, path, expected_color):
"""Return mean color of the image as (r, g, b) in float"""
image = qubesimgconverter.Image.load_from_file(path)
_, l, _ = colorsys.rgb_to_hls(
*qubesimgconverter.hex_to_float(expected_color))
def get_hls(pixels, l):
for i in xrange(0, len(pixels), 4):
r, g, b, a = tuple(ord(c) / 255. for c in pixels[i:i + 4])
if a == 0.0:
continue
h, _, s = colorsys.rgb_to_hls(r, g, b)
yield h, l, s
mean_hls = reduce(
lambda x, y: (x[0] + y[0], x[1] + y[1], x[2] + y[2]),
get_hls(image.data, l),
(0, 0, 0)
)
mean_hls = map(lambda x: x / (mean_hls[1] / l), mean_hls)
image_color = colorsys.hls_to_rgb(*mean_hls)
return image_color
def assertIconColor(self, path, expected_color):
image_color_float = self.get_image_color(path, expected_color)
expected_color_float = qubesimgconverter.hex_to_float(expected_color)
if not all(map(lambda a, b: abs(a - b) <= 0.15,
image_color_float, expected_color_float)):
self.fail(
"Icon {} is not colored as {}".format(path, expected_color))
def test_010_icon_color(self, vm=None):
if vm is None:
vm = self.vm
icons_dir = self.appmenus.icons_dir(vm)
appicons = os.listdir(icons_dir)
for icon in appicons:
self.assertIconColor(os.path.join(icons_dir, icon),
vm.label.color)
def test_011_icon_color_label_change(self):
"""Regression test for #1606"""
self.vm.label = 'green'
self.test_010_icon_color()
def test_020_clone(self):
vm2 = self.app.add_new_vm(qubes.vm.appvm.AppVM,
name=self.make_vm_name('vm2'), label='green')
vm2.clone_properties(self.vm)
vm2.clone_disk_files(self.vm)
self.test_000_created(vm=vm2)
self.test_010_icon_color(vm=vm2)
def list_tests():
return (
TC_00_Appmenus,
TC_10_AppmenusIntegration,
)