mirror of
https://github.com/cockpit-project/cockpit.git
synced 2026-11-03 21:49:50 -09:00
playground: add test page for the install dialog
Test various install dialog scenarios in a controlled environment in preparation for supporting and verifying our dnf5daemon backend implementation.
This commit is contained in:
parent
5d52d60799
commit
4b5c290f98
8 changed files with 177 additions and 23 deletions
2
files.js
2
files.js
|
|
@ -28,6 +28,7 @@ const info = {
|
|||
"playground/journal.jsx",
|
||||
"playground/remote.tsx",
|
||||
"playground/terminal.tsx",
|
||||
"playground/packagemanager.tsx",
|
||||
|
||||
"selinux/selinux.js",
|
||||
"shell/shell.jsx",
|
||||
|
|
@ -122,6 +123,7 @@ const info = {
|
|||
"playground/journal.html",
|
||||
"playground/remote.html",
|
||||
"playground/terminal.html",
|
||||
"playground/packagemanager.html",
|
||||
|
||||
"selinux/index.html",
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ QUnit.test("simple request", assert => {
|
|||
},
|
||||
test: {
|
||||
label: "Playground"
|
||||
},
|
||||
packagemanager: {
|
||||
label: "PackageManager (install dialog)"
|
||||
}
|
||||
},
|
||||
preload: ["preloaded"],
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@
|
|||
},
|
||||
"terminal": {
|
||||
"label": "Terminal"
|
||||
},
|
||||
"packagemanager": {
|
||||
"label": "PackageManager (install dialog)"
|
||||
}
|
||||
},
|
||||
"preload": [ "preloaded" ],
|
||||
|
|
|
|||
14
pkg/playground/packagemanager.html
Normal file
14
pkg/playground/packagemanager.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Cockpit Package Manager Integration</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<link href="packagemanager.css" type="text/css" rel="stylesheet" />
|
||||
<script src="../base1/cockpit.js"></script>
|
||||
<script src="packagemanager.js"></script>
|
||||
</head>
|
||||
<body class="pf-v6-m-tabular-nums">
|
||||
<div id="packagemanager"></div>
|
||||
</body>
|
||||
</html>
|
||||
50
pkg/playground/packagemanager.tsx
Normal file
50
pkg/playground/packagemanager.tsx
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import React from 'react';
|
||||
import { createRoot } from "react-dom/client";
|
||||
|
||||
import '../lib/patternfly/patternfly-6-cockpit.scss';
|
||||
|
||||
import { Button } from "@patternfly/react-core/dist/esm/components/Button/index.js";
|
||||
import { Card, CardBody, CardTitle } from '@patternfly/react-core/dist/esm/components/Card/index.js';
|
||||
import { Content } from "@patternfly/react-core/dist/esm/components/Content/index.js";
|
||||
import { Page, PageSection } from "@patternfly/react-core/dist/esm/components/Page/index.js";
|
||||
import { TextInput } from "@patternfly/react-core/dist/esm/components/TextInput/index.js";
|
||||
|
||||
import { install_dialog } from "../lib/cockpit-components-install-dialog.jsx";
|
||||
|
||||
import 'cockpit-dark-theme'; // once per page
|
||||
import 'page.scss';
|
||||
|
||||
const PackageManagerPage = () => {
|
||||
const [isInstalling, setIsInstalling] = React.useState(false);
|
||||
const [installPackages, setInstallPackages] = React.useState("");
|
||||
|
||||
const openInstallDialog = async () => {
|
||||
setIsInstalling(true);
|
||||
try {
|
||||
await install_dialog(installPackages.split(","));
|
||||
} finally {
|
||||
setIsInstalling(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Page isContentFilled className="no-masthead-sidebar">
|
||||
<PageSection hasBodyWrapper={false}>
|
||||
<Content>
|
||||
<Card>
|
||||
<CardTitle id="install-card-title">Install dialog test</CardTitle>
|
||||
<CardBody>
|
||||
<TextInput id="install-packages" value={installPackages} onChange={(_event, value) => setInstallPackages(value)} />
|
||||
<Button id="install-button" variant="primary" onClick={() => openInstallDialog()} isLoading={isInstalling}>Install Package</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Content>
|
||||
</PageSection>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const root = createRoot(document.getElementById("packagemanager")!);
|
||||
root.render(<PackageManagerPage />);
|
||||
});
|
||||
|
|
@ -18,7 +18,6 @@
|
|||
import logging
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
from collections.abc import Mapping
|
||||
|
||||
from testlib import MachineCase
|
||||
|
|
@ -153,32 +152,31 @@ Server = file://{empty_repo_dir}
|
|||
def createPackage(self, name: str, version: str, release: str, *, install: bool = False,
|
||||
postinst: str | None = None, depends: str = "",
|
||||
content: Mapping[str, Mapping[str, str] | str] | None = None,
|
||||
arch: str | None = None, provides: str | None = None, **updateinfo: str | list[str]) -> None:
|
||||
arch: str | None = None, provides: str | None = None,
|
||||
conflicts: str | None = None,
|
||||
**updateinfo: str | list[str]) -> None:
|
||||
"""Create a dummy package in repo_dir on self.machine
|
||||
|
||||
If install is True, install the package. Otherwise, update the package
|
||||
index in repo_dir.
|
||||
"""
|
||||
if provides:
|
||||
provides = f"Provides: {provides}"
|
||||
else:
|
||||
provides = ""
|
||||
|
||||
if self.backend == "apt":
|
||||
self.createDeb(name, version + '-' + release, depends, postinst, install=install, content=content,
|
||||
arch=arch, provides=provides)
|
||||
arch=arch, provides=provides, conflicts=conflicts)
|
||||
elif self.backend == "alpm":
|
||||
self.createPacmanPkg(name, version, release, depends, postinst, install=install, content=content,
|
||||
arch=arch, provides=provides)
|
||||
arch=arch, provides=provides, conflicts=conflicts)
|
||||
else:
|
||||
self.createRpm(name, version, release, depends, postinst, install=install, content=content,
|
||||
arch=arch, provides=provides)
|
||||
arch=arch, provides=provides, conflicts=conflicts)
|
||||
if updateinfo:
|
||||
self.updateInfo[name, version, release] = updateinfo
|
||||
|
||||
def createDeb(self, name: str, version: str, depends: str, postinst: str | None = None, *, install: bool,
|
||||
content: Mapping[str, Mapping[str, str] | str] | None = None,
|
||||
arch: str | None = None, provides: str | None = None) -> None:
|
||||
arch: str | None = None, provides: str | None = None,
|
||||
conflicts: str | None = None) -> None:
|
||||
"""Create a dummy deb in repo_dir on self.machine
|
||||
|
||||
If install is True, install the package. Otherwise, update the package
|
||||
|
|
@ -186,6 +184,12 @@ Server = file://{empty_repo_dir}
|
|||
"""
|
||||
m = self.machine
|
||||
|
||||
extra_fields = ""
|
||||
if provides:
|
||||
extra_fields += f"Provides: {provides}\n"
|
||||
if conflicts:
|
||||
extra_fields += f"Conflicts: {conflicts}\n"
|
||||
|
||||
if arch is None:
|
||||
arch = self.primary_arch
|
||||
deb = f"{self.repo_dir}/{name}_{version}_{arch}.deb"
|
||||
|
|
@ -202,17 +206,17 @@ Server = file://{empty_repo_dir}
|
|||
else:
|
||||
m.write(dest, data)
|
||||
m.execute(f"mkdir -p {self.repo_dir}")
|
||||
m.write("/tmp/b/DEBIAN/control", textwrap.dedent(f"""
|
||||
Package: {name}
|
||||
Version: {version}
|
||||
Priority: optional
|
||||
Section: test
|
||||
Maintainer: foo
|
||||
Depends: {depends}
|
||||
Architecture: {arch}
|
||||
Description: dummy {name}
|
||||
{provides}
|
||||
"""))
|
||||
m.write("/tmp/b/DEBIAN/control", f"""
|
||||
Package: {name}
|
||||
Version: {version}
|
||||
Priority: optional
|
||||
Section: test
|
||||
Maintainer: foo
|
||||
Depends: {depends}
|
||||
Architecture: {arch}
|
||||
Description: dummy {name}
|
||||
{extra_fields}
|
||||
""")
|
||||
|
||||
cmd = f"""set -e
|
||||
{postinstcode}
|
||||
|
|
@ -229,7 +233,8 @@ Server = file://{empty_repo_dir}
|
|||
install: bool,
|
||||
content: Mapping[str, Mapping[str, str] | str] | None = None,
|
||||
arch: str | None = None,
|
||||
provides: str | None = None) -> None:
|
||||
provides: str | None = None,
|
||||
conflicts: str | None = None) -> None:
|
||||
"""Create a dummy rpm in repo_dir on self.machine
|
||||
|
||||
If install is True, install the package. Otherwise, update the package
|
||||
|
|
@ -241,6 +246,10 @@ Server = file://{empty_repo_dir}
|
|||
postcode = ''
|
||||
if requires:
|
||||
requires = f"Requires: {requires}\n"
|
||||
|
||||
conflicts = f"Conflicts: {conflicts}\n" if conflicts is not None else ""
|
||||
provides = f"Provides: {provides}\n" if provides is not None else ""
|
||||
|
||||
if arch is None:
|
||||
arch = self.primary_arch
|
||||
installcmds = f"touch $RPM_BUILD_ROOT/stamp-{name}-{version}-{release}\n"
|
||||
|
|
@ -266,6 +275,7 @@ License: BSD
|
|||
{provides}
|
||||
{architecture}
|
||||
{requires}
|
||||
{conflicts}
|
||||
|
||||
%define _build_id_links none
|
||||
|
||||
|
|
@ -296,7 +306,8 @@ rm -rf ~/rpmbuild
|
|||
install: bool,
|
||||
content: Mapping[str, Mapping[str, str] | str] | None = None,
|
||||
arch: str | None = None,
|
||||
provides: str | None = None) -> None:
|
||||
provides: str | None = None,
|
||||
conflicts: str | None = None) -> None:
|
||||
"""Create a dummy pacman package in repo_dir on self.machine
|
||||
|
||||
If install is True, install the package. Otherwise, update the package
|
||||
|
|
@ -306,6 +317,9 @@ rm -rf ~/rpmbuild
|
|||
if arch is None:
|
||||
arch = 'any'
|
||||
|
||||
conflicts = f'conflicts=("{conflicts}")' if conflicts else ''
|
||||
provides = f'provides=("{provides}")' if provides else ''
|
||||
|
||||
sources = ""
|
||||
installcmds = 'package() {\n'
|
||||
if content is not None:
|
||||
|
|
@ -339,6 +353,8 @@ pkgdesc="dummy {name}"
|
|||
pkgrel={release}
|
||||
arch=({arch})
|
||||
depends=({requires})
|
||||
{conflicts}
|
||||
{provides}
|
||||
options=(!debug)
|
||||
{sources}
|
||||
|
||||
|
|
|
|||
|
|
@ -1841,5 +1841,70 @@ WantedBy=basic.target
|
|||
b.wait_visible("#automatic-updates-dialog")
|
||||
|
||||
|
||||
@testlib.skipOstree("Image uses OSTree")
|
||||
@testlib.nondestructive
|
||||
class TestInstallDialog(NoSubManCase):
|
||||
|
||||
def show_install_dialog(self, *packages: str):
|
||||
b = self.browser
|
||||
b.set_input_text("#install-packages", ",".join(packages))
|
||||
b.click("#install-button")
|
||||
b.wait_in_text("#dialog", "Install software")
|
||||
|
||||
def install_packages(self):
|
||||
b = self.browser
|
||||
b.click("#dialog button:contains('Install')")
|
||||
# assert some progress
|
||||
b.wait_not_present("#dialog")
|
||||
|
||||
def testBasic(self):
|
||||
b = self.browser
|
||||
m = self.machine
|
||||
|
||||
# dummy package
|
||||
self.createPackage("dummy", "1", "1")
|
||||
|
||||
# package which depends on another package
|
||||
self.createPackage("test-webserver", "998", "1", depends="test-webengine")
|
||||
self.createPackage("test-webengine", "998", "1")
|
||||
|
||||
# package which conflicts with another package
|
||||
self.createPackage("ok-software", "998", "1", install=True)
|
||||
self.createPackage("breaking-software", "999", "1", conflicts="ok-software")
|
||||
|
||||
# Test error condition, package not being available yet
|
||||
self.login_and_go('/playground/packagemanager')
|
||||
b.wait_in_text("#install-card-title", "Install dialog test")
|
||||
self.show_install_dialog("dummy")
|
||||
b.wait_in_text("#dialog .pf-v6-c-alert__title", "dummy is not available from any repository.")
|
||||
b.wait_visible("#dialog button.pf-m-primary:disabled")
|
||||
b.click("#dialog button.cancel")
|
||||
b.wait_not_present("#dialog")
|
||||
|
||||
# Install dummy which is now available
|
||||
# Also works around https://github.com/PackageKit/PackageKit/issues/893
|
||||
self.enableRepo()
|
||||
if self.backend != "dnf5":
|
||||
m.execute("pkcon refresh force")
|
||||
|
||||
self.show_install_dialog("dummy")
|
||||
b.wait_in_text("#dialog", "dummy will be installed.")
|
||||
self.install_packages()
|
||||
|
||||
# Test that additional installed packages are shown in the dialog
|
||||
b.wait_in_text("#install-card-title", "Install dialog test")
|
||||
self.show_install_dialog("test-webserver")
|
||||
b.wait_in_text("#dialog", "test-webserver will be installed.")
|
||||
b.wait_text("#dialog .scale-up-ct", "Additional packages:test-webengine")
|
||||
self.install_packages()
|
||||
|
||||
# Test conflicting packages are shown as removed
|
||||
self.show_install_dialog("breaking-software")
|
||||
b.wait_in_text("#dialog", "breaking-software will be installed.")
|
||||
# The svg icon creates a tiny bit of whitespace
|
||||
b.wait_text("#dialog .scale-up-ct", " Removals:ok-software")
|
||||
self.install_packages()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
testlib.test_main()
|
||||
|
|
|
|||
|
|
@ -332,6 +332,7 @@ class TestStoragePackagesNFS(packagelib.PackageCase, storagelib.StorageCase):
|
|||
# anything on the Debian test images, for unknown reasons. So
|
||||
# we install a dummy package to warm up all parts of the
|
||||
# machinery and distribute the fluids evenly.
|
||||
# https://github.com/PackageKit/PackageKit/issues/893
|
||||
#
|
||||
if "debian" in m.image or "ubuntu" in m.image:
|
||||
m.execute("pkcon refresh; pkcon install -y dummy")
|
||||
|
|
|
|||
Loading…
Reference in a new issue