cockpit/test/verify/check-apps

324 lines
12 KiB
Python
Executable File

#!/usr/bin/python3 -cimport os, sys; os.execv(os.path.dirname(sys.argv[1]) + "/../common/pywrap", sys.argv)
# This file is part of Cockpit.
#
# Copyright (C) 2017 Red Hat, Inc.
#
# Cockpit is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or
# (at your option) any later version.
#
# Cockpit 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
from packagelib import PackageCase
from testlib import nondestructive, skipImage, skipOstree, test_main
@skipImage("TODO: bug in refreshing removed/installed state on Arch Linux", "arch")
@skipOstree("Not supported")
@nondestructive
class TestApps(PackageCase):
def setUp(self):
super().setUp()
self.appstream_collection = set()
self.machine.upload(["verify/files/test.png"], "/var/tmp/")
def createAppStreamPackage(self, name, version, revision):
self.createPackage(name, version, revision, content={
f"/usr/share/metainfo/org.cockpit-project.{name}.metainfo.xml": f"""
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<id>org.cockpit-project.{name}</id>
<icon type="local">/usr/share/pixmaps/test.png</icon>
<name>{name}</name>
<summary>An application for testing</summary>
<launchable type="cockpit-manifest">{name}</launchable>
</component>
""",
"/usr/share/pixmaps/test.png": {"path": "/var/tmp/test.png"}})
self.appstream_collection.add(name)
def createAppStreamRepoPackage(self):
body = ""
for p in self.appstream_collection:
body += f"""
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<id>org.cockpit-project.{p}</id>
<icon type="cached">test.png</icon>
<name>{p}</name>
<summary>An application for testing</summary>
<launchable type="cockpit-manifest">{p}</launchable>
<description>
<p>DESCRIPTION:none</p>
</description>
<url type="homepage">https://{p}.com</url>
<pkgname>{p}</pkgname>
</component>
"""
self.machine.execute("mkdir -p /usr/share/app-info/xmls")
self.createPackage("appstream-data-test", "1.0", "1", content={
"/usr/share/app-info/xmls/test.xml": f"""
<components origin="test">
{body}
</components>
""",
"/usr/share/app-info/icons/test/64x64/test.png": {"path": "/var/tmp/test.png"}})
self.enableRepo()
self.machine.execute("systemctl stop packagekit; pkcon refresh force")
# ignore the corresponding journal entry
self.allow_journal_messages("org.freedesktop.PackageKit.*org.freedesktop.DBus.Error.NoReply.*")
def testBasic(self, urlroot=""):
b = self.browser
m = self.machine
self.allow_journal_messages("can't remove watch: Invalid argument")
self.restore_dir("/usr/share/metainfo", reboot_safe=True)
self.restore_dir("/usr/share/app-info", reboot_safe=True)
self.restore_dir("/var/cache/app-info", reboot_safe=True)
# Make sure none of the appstream directories exist. They
# will be created later and we need to cope with that.
m.execute("rm -rf /usr/share/metainfo /usr/share/app-info /var/cache/app-info")
# instead of the actual distro packages, use our own fake repo data package
self.write_file("/etc/cockpit/apps.override.json",
'{ "config": { "appstream_data_packages": [ "appstream-data-test" ] } }')
if urlroot != "":
m.write("/etc/cockpit/cockpit.conf", f"[WebService]\nUrlRoot={urlroot}")
self.login_and_go("/apps", urlroot=urlroot)
b.wait_visible(".pf-v5-c-empty-state")
self.createAppStreamPackage("app-1", "1.0", "1")
self.createAppStreamRepoPackage()
# Refresh package info
b.click("#refresh")
with b.wait_timeout(30):
b.click(".app-list #app-1")
b.wait_visible('a[href="https://app-1.com"]')
b.wait_visible(f'#app-page img[src^="{urlroot}/cockpit/channel/"]')
b.click(".pf-v5-c-breadcrumb a:contains('Applications')")
b.wait_visible("#list-page")
b.wait_not_present("#app-page")
b.click(".app-list .pf-v5-c-data-list__item-row:contains('app-1') button:contains('Install')")
b.wait_visible(".app-list .pf-v5-c-data-list__item-row:contains('app-1') button:contains('Remove')")
b.wait_visible(f".app-list .pf-v5-c-data-list__item-row:contains('app-1') img[src^='{urlroot}/cockpit/channel/']")
m.execute("test -f /stamp-app-1-1.0-1")
b.click(".app-list .pf-v5-c-data-list__item-row:contains('app-1') button:contains('Remove')")
b.wait_visible(".app-list .pf-v5-c-data-list__item-row:contains('app-1') button:contains('Install')")
b.wait_visible(f".app-list .pf-v5-c-data-list__item-row:contains('app-1') img[src^='{urlroot}/cockpit/channel/']")
m.execute("! test -f /stamp-app-1-1.0-1")
def testWithUrlRoot(self):
self.testBasic(urlroot="/webcon")
def testL10N(self):
b = self.browser
m = self.machine
# Switching to a language might produce these messages, which seem to be harmless.
self.allow_journal_messages("invalid or unusable locale.*",
"Error .* data: Connection reset by peer")
# Reset everything
m.execute("for d in /usr/share/metainfo /usr/share/app-info /var/cache/app-info; do mkdir -p $d; mount -t tmpfs tmpfs $d; done")
self.addCleanup(m.execute, "for d in /usr/share/metainfo /usr/share/app-info /var/cache/app-info; do umount $d; done")
self.login_and_go("/apps")
b.wait_visible(".pf-v5-c-empty-state")
m.write("/usr/share/app-info/xmls/test.xml", """
<components origin="test">
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<id>org.cockpit-project.foo</id>
<name>NAME:none</name>
<name xml:lang="de">NAME:de</name>
<summary>SUMMARY:none</summary>
<summary xml:lang="de">SUMMARY:de</summary>
<description>
<p>DESCRIPTION:none</p>
<p xml:lang="de">DESCRIPTION:de</p>
</description>
<launchable type="cockpit-manifest">foo</launchable>
<pkgname>foo</pkgname>
</component>
</components>""")
b.wait_visible(".app-list .pf-v5-c-data-list__item-row:contains('SUMMARY:none') button:contains('Install')")
b.click(".app-list .pf-v5-c-data-list__item-row:contains('SUMMARY:none') .pf-m-inline")
b.wait_visible(".app-description:contains('DESCRIPTION:none')")
def set_lang(lang):
b.switch_to_top()
b.open_session_menu()
b.click(".display-language-menu")
b.wait_visible('#display-language-modal')
if self.system_before(242): # Changed in #15667
b.set_val("#display-language-modal select", lang)
else:
b.click(f'#display-language-modal li[data-value={lang}] button')
b.click("#display-language-modal footer button.pf-m-primary")
b.wait_language(lang)
b.enter_page("/apps")
set_lang("de-de")
b.wait_visible(".app-description:contains('DESCRIPTION:de')")
b.wait_not_present(".app-description:contains('DESCRIPTION:none')")
set_lang("ja-jp")
b.wait_visible(".app-description:contains('DESCRIPTION:none')")
b.wait_not_present(".app-description:contains('DESCRIPTION:de')")
# like in the general whitelist, but translated
self.allow_journal_messages("xargs: basename: .*Signal 13.*")
def testBrokenXML(self):
b = self.browser
m = self.machine
# Reset everything
m.execute("for d in /usr/share/metainfo /usr/share/app-info /var/cache/app-info; do mkdir -p $d; mount -t tmpfs tmpfs $d; done")
self.addCleanup(m.execute, "for d in /usr/share/metainfo /usr/share/app-info /var/cache/app-info; do umount $d; done")
self.login_and_go("/apps")
b.wait_visible(".pf-v5-c-empty-state")
self.allow_journal_messages(".*/usr/share/app-info/xmls/test.xml.*",
".*xml.etree.ElementTree.ParseError.*")
def reset():
m.write("/usr/share/app-info/xmls/test.xml", """
<components origin="test">
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<id>org.cockpit-project.test</id>
<name>Name</name>
<summary>Summary</summary>
<description>
<p>Description</p>
</description>
<launchable type="cockpit-manifest">foo</launchable>
<pkgname>foo</pkgname>
</component>
</components>""")
b.wait_not_present(".pf-v5-c-empty-state")
b.wait_visible(".app-list .pf-v5-c-data-list__item-row:contains('Summary')")
# First lay down some good XML so that we can later detect the reaction to broken XML.
reset()
# Unparsable
m.write("/usr/share/app-info/xmls/test.xml", """
This <is <not XML.
""")
b.wait_visible(".pf-v5-c-empty-state")
reset()
# Not really AppStream
m.write("/usr/share/app-info/xmls/test.xml", """
<foo></foo>
""")
b.wait_visible(".pf-v5-c-empty-state")
reset()
# No origin
m.write("/usr/share/app-info/xmls/test.xml", """
<components>
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<id>org.cockpit-project.test</id>
<name>Name</name>
<summary>Summary</summary>
<description>
<p>Description</p>
</description>
<launchable type="cockpit-manifest">foo</launchable>
<pkgname>foo</pkgname>
</component>
</components>""")
b.wait_visible(".pf-v5-c-empty-state")
reset()
# No package
m.write("/usr/share/app-info/xmls/test.xml", """
<components origin="test">
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<id>org.cockpit-project.test</id>
<name>Name</name>
<summary>Summary</summary>
<description>
<p>Description</p>
</description>
<launchable type="cockpit-manifest">foo</launchable>
</component>
</components>""")
b.wait_visible(".pf-v5-c-empty-state")
reset()
# No id
m.write("/usr/share/app-info/xmls/test.xml", """
<components origin="test">
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<name>Name</name>
<summary>No description</summary>
<launchable type="cockpit-manifest">foo</launchable>
<pkgname>foo</pkgname>
</component>
</components>""")
b.wait_visible(".pf-v5-c-empty-state")
reset()
# Error (launchable without type) in earlier entry, shouldn't affect the later entry
m.write("/usr/share/app-info/xmls/test.xml", """
<components origin="test">
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<id>org.cockpit-project.test2</id>
<name>Name</name>
<summary>Summary</summary>
<description>
<p>Description 2</p>
</description>
<launchable>foo</launchable>
<pkgname>foo</pkgname>
</component>
<component type="addon">
<extends>org.cockpit_project.cockpit</extends>
<id>org.cockpit-project.test</id>
<name>Name</name>
<summary>Summary 2</summary>
<description>
<p>Description</p>
</description>
<launchable type="cockpit-manifest">foo</launchable>
<pkgname>foo</pkgname>
</component>
</components>""")
b.wait_not_present(".pf-v5-c-empty-state")
b.wait_visible(".app-list .pf-v5-c-data-list__item-row:contains('Summary 2')")
if __name__ == '__main__':
test_main()