split installprogress controller into install (server) and progress (client)
This commit is contained in:
parent
92252f8119
commit
3c30e14313
|
@ -1,6 +1,8 @@
|
|||
[encoding: UTF-8]
|
||||
subiquity/client/client.py
|
||||
subiquity/client/controller.py
|
||||
subiquity/client/controllers/__init__.py
|
||||
subiquity/client/controllers/progress.py
|
||||
subiquity/client/__init__.py
|
||||
subiquity/client/keycodes.py
|
||||
subiquity/cmd/common.py
|
||||
|
@ -30,7 +32,6 @@ subiquity/controllers/error.py
|
|||
subiquity/controllers/filesystem.py
|
||||
subiquity/controllers/identity.py
|
||||
subiquity/controllers/__init__.py
|
||||
subiquity/controllers/installprogress.py
|
||||
subiquity/controllers/keyboard.py
|
||||
subiquity/controllers/mirror.py
|
||||
subiquity/controllers/network.py
|
||||
|
@ -122,6 +123,7 @@ subiquity/models/tests/test_mirror.py
|
|||
subiquity/models/tests/test_subiquity.py
|
||||
subiquity/server/controller.py
|
||||
subiquity/server/controllers/__init__.py
|
||||
subiquity/server/controllers/install.py
|
||||
subiquity/server/dryrun.py
|
||||
subiquity/server/errors.py
|
||||
subiquity/server/__init__.py
|
||||
|
|
|
@ -89,7 +89,9 @@ class SubiquityClient(TuiApplication):
|
|||
def make_ui(self):
|
||||
return SubiquityUI(self, self.help_menu)
|
||||
|
||||
controllers = []
|
||||
controllers = [
|
||||
"Progress",
|
||||
]
|
||||
|
||||
def __init__(self, opts):
|
||||
if is_linux_tty():
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# Copyright 2020 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .progress import ProgressController
|
||||
|
||||
__all__ = [
|
||||
'ProgressController',
|
||||
]
|
|
@ -0,0 +1,121 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import aiohttp
|
||||
|
||||
from subiquitycore.context import with_context
|
||||
|
||||
from subiquity.client.controller import SubiquityTuiController
|
||||
from subiquity.common.types import InstallState
|
||||
from subiquity.ui.views.installprogress import (
|
||||
InstallRunning,
|
||||
ProgressView,
|
||||
)
|
||||
|
||||
|
||||
log = logging.getLogger("subiquity.client.controllers.progress")
|
||||
|
||||
|
||||
class ProgressController(SubiquityTuiController):
|
||||
|
||||
endpoint_name = 'install'
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.progress_view = ProgressView(self)
|
||||
self.install_state = None
|
||||
self.crash_report_ref = None
|
||||
self.answers = app.answers.get("InstallProgress", {})
|
||||
|
||||
def event(self, event):
|
||||
if event["SUBIQUITY_EVENT_TYPE"] == "start":
|
||||
self.progress_view.event_start(
|
||||
event["SUBIQUITY_CONTEXT_ID"],
|
||||
event.get("SUBIQUITY_CONTEXT_PARENT_ID"),
|
||||
event["MESSAGE"])
|
||||
elif event["SUBIQUITY_EVENT_TYPE"] == "finish":
|
||||
self.progress_view.event_finish(
|
||||
event["SUBIQUITY_CONTEXT_ID"])
|
||||
|
||||
def log_line(self, event):
|
||||
log_line = event['MESSAGE']
|
||||
self.progress_view.add_log_line(log_line)
|
||||
|
||||
def cancel(self):
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
self.app.aio_loop.create_task(self._wait_status())
|
||||
|
||||
def click_reboot(self):
|
||||
self.app.aio_loop.create_task(self.send_reboot_and_wait())
|
||||
|
||||
async def send_reboot_and_wait(self):
|
||||
try:
|
||||
await self.app.client.reboot.POST()
|
||||
except aiohttp.ClientError:
|
||||
pass
|
||||
self.app.exit()
|
||||
|
||||
@with_context()
|
||||
async def _wait_status(self, context):
|
||||
install_running = None
|
||||
while True:
|
||||
try:
|
||||
install_status = await self.endpoint.status.GET(
|
||||
cur=self.install_state)
|
||||
except aiohttp.ClientError:
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
self.install_state = install_status.state
|
||||
|
||||
self.progress_view.update_for_state(self.install_state)
|
||||
if self.ui.body is self.progress_view:
|
||||
self.ui.set_header(self.progress_view.title)
|
||||
|
||||
if install_status.error is not None:
|
||||
if self.crash_report_ref is None:
|
||||
self.crash_report_ref = install_status.error
|
||||
self.ui.set_body(self.progress_view)
|
||||
self.app.show_error_report(self.crash_report_ref)
|
||||
|
||||
if self.install_state == InstallState.NEEDS_CONFIRMATION:
|
||||
if self.showing:
|
||||
self.app.show_confirm_install()
|
||||
|
||||
if self.install_state == InstallState.RUNNING:
|
||||
if install_status.confirming_tty != self.app.our_tty:
|
||||
install_running = InstallRunning(
|
||||
self.app, install_status.confirming_tty)
|
||||
self.app.add_global_overlay(install_running)
|
||||
else:
|
||||
if install_running is not None:
|
||||
self.app.remove_global_overlay(install_running)
|
||||
install_running = None
|
||||
|
||||
if self.install_state == InstallState.DONE:
|
||||
if self.answers.get('reboot', False):
|
||||
self.click_reboot()
|
||||
|
||||
def make_ui(self):
|
||||
if self.install_state == InstallState.NEEDS_CONFIRMATION:
|
||||
self.app.show_confirm_install()
|
||||
return self.progress_view
|
||||
|
||||
def run_answers(self):
|
||||
pass
|
|
@ -20,6 +20,8 @@ from subiquity.common.types import (
|
|||
ApplicationState,
|
||||
ApplicationStatus,
|
||||
ErrorReportRef,
|
||||
InstallState,
|
||||
InstallStatus,
|
||||
)
|
||||
|
||||
|
||||
|
@ -55,3 +57,7 @@ class API:
|
|||
class crash:
|
||||
def GET() -> None:
|
||||
"""Requests to this method will fail with a HTTP 500."""
|
||||
|
||||
class install:
|
||||
class status:
|
||||
def GET(cur: Optional[InstallState] = None) -> InstallStatus: ...
|
||||
|
|
|
@ -183,3 +183,10 @@ class InstallState(enum.Enum):
|
|||
UU_CANCELLING = enum.auto()
|
||||
DONE = enum.auto()
|
||||
ERROR = enum.auto()
|
||||
|
||||
|
||||
@attr.s(auto_attribs=True)
|
||||
class InstallStatus:
|
||||
state: InstallState
|
||||
confirming_tty: str = ''
|
||||
error: Optional[ErrorReportRef] = None
|
||||
|
|
|
@ -19,7 +19,6 @@ from .debconf import DebconfController
|
|||
from .error import ErrorController
|
||||
from .filesystem import FilesystemController
|
||||
from .identity import IdentityController
|
||||
from .installprogress import InstallProgressController
|
||||
from .keyboard import KeyboardController
|
||||
from .mirror import MirrorController
|
||||
from .network import NetworkController
|
||||
|
@ -40,7 +39,6 @@ __all__ = [
|
|||
'ErrorController',
|
||||
'FilesystemController',
|
||||
'IdentityController',
|
||||
'InstallProgressController',
|
||||
'KeyboardController',
|
||||
'LateController',
|
||||
'MirrorController',
|
||||
|
|
|
@ -12,3 +12,9 @@
|
|||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .install import InstallController
|
||||
|
||||
__all__ = [
|
||||
'InstallController',
|
||||
]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2015 Canonical, Ltd.
|
||||
# Copyright 2020 Canonical, Ltd.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as
|
||||
|
@ -21,7 +21,7 @@ import re
|
|||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
from typing import Optional
|
||||
|
||||
from curtin.commands.install import (
|
||||
ERROR_TARFILE,
|
||||
|
@ -29,13 +29,10 @@ from curtin.commands.install import (
|
|||
)
|
||||
from curtin.util import write_file
|
||||
|
||||
from systemd import journal
|
||||
|
||||
import yaml
|
||||
|
||||
from subiquitycore.async_helpers import (
|
||||
run_in_thread,
|
||||
schedule_task,
|
||||
)
|
||||
from subiquitycore.context import Status, with_context
|
||||
from subiquitycore.utils import (
|
||||
|
@ -43,14 +40,18 @@ from subiquitycore.utils import (
|
|||
astart_command,
|
||||
)
|
||||
|
||||
from subiquity.common.apidef import API
|
||||
from subiquity.common.errorreport import ErrorReportKind
|
||||
from subiquity.common.types import InstallState
|
||||
from subiquity.controller import SubiquityTuiController
|
||||
from subiquity.server.controller import (
|
||||
SubiquityController,
|
||||
)
|
||||
from subiquity.common.types import (
|
||||
InstallState,
|
||||
InstallStatus,
|
||||
)
|
||||
from subiquity.journald import journald_listen
|
||||
from subiquity.ui.views.installprogress import ProgressView
|
||||
|
||||
|
||||
log = logging.getLogger("subiquitycore.controller.installprogress")
|
||||
log = logging.getLogger("subiquity.server.controllers.install")
|
||||
|
||||
|
||||
class TracebackExtractor:
|
||||
|
@ -72,18 +73,16 @@ class TracebackExtractor:
|
|||
self.traceback.append(line)
|
||||
|
||||
|
||||
class InstallProgressController(SubiquityTuiController):
|
||||
class InstallController(SubiquityController):
|
||||
|
||||
endpoint = API.install
|
||||
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
self.model = app.base_model
|
||||
self.progress_view = ProgressView(self)
|
||||
self.crash_report_ref = None
|
||||
self._install_state = InstallState.NOT_STARTED
|
||||
|
||||
self.reboot_clicked = asyncio.Event()
|
||||
if self.answers.get('reboot', False):
|
||||
self.reboot_clicked.set()
|
||||
self._install_state_event = asyncio.Event()
|
||||
self.error_ref = None
|
||||
|
||||
self.unattended_upgrades_proc = None
|
||||
self.unattended_upgrades_ctx = None
|
||||
|
@ -91,71 +90,53 @@ class InstallProgressController(SubiquityTuiController):
|
|||
self.tb_extractor = TracebackExtractor()
|
||||
self.curtin_event_contexts = {}
|
||||
|
||||
def event(self, event):
|
||||
if event["SUBIQUITY_EVENT_TYPE"] == "start":
|
||||
self.progress_view.event_start(
|
||||
event["SUBIQUITY_CONTEXT_ID"],
|
||||
event.get("SUBIQUITY_CONTEXT_PARENT_ID"),
|
||||
event["MESSAGE"])
|
||||
elif event["SUBIQUITY_EVENT_TYPE"] == "finish":
|
||||
self.progress_view.event_finish(
|
||||
event["SUBIQUITY_CONTEXT_ID"])
|
||||
|
||||
def log_line(self, event):
|
||||
log_line = event['MESSAGE']
|
||||
self.progress_view.add_log_line(log_line)
|
||||
|
||||
def interactive(self):
|
||||
return self.app.interactive()
|
||||
return True
|
||||
|
||||
async def status_GET(
|
||||
self, cur: Optional[InstallState] = None) -> InstallStatus:
|
||||
if cur == self.install_state:
|
||||
await self._install_state_event.wait()
|
||||
return InstallStatus(
|
||||
self.install_state,
|
||||
self.app.confirming_tty,
|
||||
self.error_ref)
|
||||
|
||||
def stop_uu(self):
|
||||
if self.install_state == InstallState.UU_RUNNING:
|
||||
self.update_state(InstallState.UU_CANCELLING)
|
||||
self.app.aio_loop.create_task(self.stop_unattended_upgrades())
|
||||
|
||||
def start(self):
|
||||
self.install_task = schedule_task(self.install())
|
||||
|
||||
@with_context()
|
||||
async def apply_autoinstall_config(self, context):
|
||||
await self.install_task
|
||||
self.app.reboot_on_exit = True
|
||||
self.install_task = self.app.aio_loop.create_task(self.install())
|
||||
|
||||
@property
|
||||
def install_state(self):
|
||||
return self._install_state
|
||||
|
||||
def update_state(self, state):
|
||||
self._install_state_event.set()
|
||||
self._install_state_event.clear()
|
||||
self._install_state = state
|
||||
self.progress_view.update_for_state(state)
|
||||
|
||||
def tpath(self, *path):
|
||||
return os.path.join(self.model.target, *path)
|
||||
|
||||
def curtin_error(self):
|
||||
self.update_state(InstallState.ERROR)
|
||||
kw = {}
|
||||
if sys.exc_info()[0] is not None:
|
||||
log.exception("curtin_error")
|
||||
self.progress_view.add_log_line(traceback.format_exc())
|
||||
# send traceback.format_exc() to journal?
|
||||
if self.tb_extractor.traceback:
|
||||
kw["Traceback"] = "\n".join(self.tb_extractor.traceback)
|
||||
crash_report = self.app.make_apport_report(
|
||||
ErrorReportKind.INSTALL_FAIL, "install failed", interrupt=False,
|
||||
**kw)
|
||||
if crash_report is not None:
|
||||
self.crash_report_ref = crash_report.ref()
|
||||
self.progress_view.finish_all()
|
||||
self.progress_view.set_status(
|
||||
('info_error', _("An error has occurred")))
|
||||
if not self.showing:
|
||||
self.app.controllers.index = self.controller_index - 1
|
||||
self.app.next_screen()
|
||||
self.update_state(InstallState.ERROR)
|
||||
if self.crash_report_ref is not None:
|
||||
self.app.show_error_report(self.crash_report_ref)
|
||||
self.error_ref = self.app.make_apport_report(
|
||||
ErrorReportKind.INSTALL_FAIL, "install failed", **kw).ref()
|
||||
|
||||
def logged_command(self, cmd):
|
||||
return ['systemd-cat', '--level-prefix=false',
|
||||
'--identifier=' + self.app.log_syslog_id] + cmd
|
||||
|
||||
def log_event(self, event):
|
||||
self.curtin_log(event)
|
||||
|
||||
def curtin_event(self, event):
|
||||
e = {
|
||||
"EVENT_TYPE": "???",
|
||||
|
@ -189,7 +170,7 @@ class InstallProgressController(SubiquityTuiController):
|
|||
if curtin_ctx is not None:
|
||||
curtin_ctx.exit(result=status)
|
||||
|
||||
def curtin_log(self, event):
|
||||
def log_event(self, event):
|
||||
self.tb_extractor.feed(event['MESSAGE'])
|
||||
|
||||
def _write_config(self, path, config):
|
||||
|
@ -202,7 +183,7 @@ class InstallProgressController(SubiquityTuiController):
|
|||
def _get_curtin_command(self):
|
||||
config_file_name = 'subiquity-curtin-install.conf'
|
||||
|
||||
if self.opts.dry_run:
|
||||
if self.app.opts.dry_run:
|
||||
config_location = os.path.join('.subiquity/', config_file_name)
|
||||
log_location = '.subiquity/install.log'
|
||||
event_file = "examples/curtin-events.json"
|
||||
|
@ -219,9 +200,9 @@ class InstallProgressController(SubiquityTuiController):
|
|||
config_location, 'install']
|
||||
log_location = INSTALL_LOG
|
||||
|
||||
self._write_config(
|
||||
config_location,
|
||||
self.model.render(syslog_identifier=self._event_syslog_id))
|
||||
ident = self._event_syslog_id
|
||||
self._write_config(config_location,
|
||||
self.model.render(syslog_identifier=ident))
|
||||
|
||||
self.app.note_file_for_apport("CurtinConfig", config_location)
|
||||
self.app.note_file_for_apport("CurtinLog", log_location)
|
||||
|
@ -235,10 +216,10 @@ class InstallProgressController(SubiquityTuiController):
|
|||
sys.executable, '-m', 'curtin', 'unmount',
|
||||
'-t', target,
|
||||
]
|
||||
if self.opts.dry_run:
|
||||
if self.app.opts.dry_run:
|
||||
cmd = ['sleep', str(0.2/self.app.scale_factor)]
|
||||
await arun_command(cmd)
|
||||
if not self.opts.dry_run:
|
||||
if not self.app.opts.dry_run:
|
||||
shutil.rmtree(target)
|
||||
|
||||
@with_context(
|
||||
|
@ -250,7 +231,7 @@ class InstallProgressController(SubiquityTuiController):
|
|||
loop = self.app.aio_loop
|
||||
|
||||
fds = [
|
||||
journald_listen(loop, [self.app.log_syslog_id], self.curtin_log),
|
||||
journald_listen(loop, [self.app.log_syslog_id], self.log_event),
|
||||
journald_listen(loop, [self._event_syslog_id], self.curtin_event),
|
||||
]
|
||||
|
||||
|
@ -258,32 +239,24 @@ class InstallProgressController(SubiquityTuiController):
|
|||
|
||||
log.debug('curtin install cmd: {}'.format(curtin_cmd))
|
||||
|
||||
async with self.app.install_lock_file.exclusive():
|
||||
try:
|
||||
our_tty = os.ttyname(0)
|
||||
except OSError:
|
||||
# This is a gross hack for testing in travis.
|
||||
our_tty = "/dev/not a tty"
|
||||
self.app.install_lock_file.write_content(our_tty)
|
||||
journal.send("starting install", SYSLOG_IDENTIFIER="subiquity")
|
||||
try:
|
||||
cp = await arun_command(
|
||||
self.logged_command(curtin_cmd), check=True)
|
||||
finally:
|
||||
for fd in fds:
|
||||
loop.remove_reader(fd)
|
||||
try:
|
||||
cp = await arun_command(
|
||||
self.logged_command(curtin_cmd), check=True)
|
||||
finally:
|
||||
for fd in fds:
|
||||
loop.remove_reader(fd)
|
||||
|
||||
log.debug('curtin_install completed: %s', cp.returncode)
|
||||
|
||||
def cancel(self):
|
||||
pass
|
||||
|
||||
@with_context()
|
||||
async def install(self, *, context):
|
||||
context.set('is-install-context', True)
|
||||
try:
|
||||
await asyncio.wait(
|
||||
{e.wait() for e in self.model.install_events})
|
||||
await asyncio.wait({e.wait() for e in self.model.install_events})
|
||||
|
||||
if not self.app.interactive():
|
||||
if 'autoinstall' in self.app.kernel_cmdline:
|
||||
self.model.confirm()
|
||||
|
||||
self.update_state(InstallState.NEEDS_CONFIRMATION)
|
||||
|
||||
|
@ -315,16 +288,10 @@ class InstallProgressController(SubiquityTuiController):
|
|||
self.update_state(InstallState.DONE)
|
||||
except Exception:
|
||||
self.curtin_error()
|
||||
if not self.interactive():
|
||||
raise
|
||||
|
||||
async def move_on(self):
|
||||
await self.install_task
|
||||
self.app.next_screen()
|
||||
|
||||
async def drain_curtin_events(self, *, context):
|
||||
waited = 0.0
|
||||
while self.progress_view.ongoing and waited < 5.0:
|
||||
while len(self.curtin_event_contexts) > 1 and waited < 5.0:
|
||||
await asyncio.sleep(0.1)
|
||||
waited += 0.1
|
||||
log.debug("waited %s seconds for events to drain", waited)
|
||||
|
@ -356,7 +323,7 @@ class InstallProgressController(SubiquityTuiController):
|
|||
name="install_{package}",
|
||||
description="installing {package}")
|
||||
async def install_package(self, *, context, package):
|
||||
if self.opts.dry_run:
|
||||
if self.app.opts.dry_run:
|
||||
cmd = ["sleep", str(2/self.app.scale_factor)]
|
||||
else:
|
||||
cmd = [
|
||||
|
@ -368,7 +335,7 @@ class InstallProgressController(SubiquityTuiController):
|
|||
|
||||
@with_context(description="restoring apt configuration")
|
||||
async def restore_apt_config(self, context):
|
||||
if self.opts.dry_run:
|
||||
if self.app.opts.dry_run:
|
||||
cmds = [["sleep", str(1/self.app.scale_factor)]]
|
||||
else:
|
||||
cmds = [
|
||||
|
@ -395,7 +362,7 @@ class InstallProgressController(SubiquityTuiController):
|
|||
env = os.environ.copy()
|
||||
env["APT_CONFIG"] = apt_conf.name[len(self.model.target):]
|
||||
self.unattended_upgrades_ctx = context
|
||||
if self.opts.dry_run:
|
||||
if self.app.opts.dry_run:
|
||||
self.unattended_upgrades_proc = await astart_command(
|
||||
self.logged_command(
|
||||
["sleep", str(5/self.app.scale_factor)]), env=env)
|
||||
|
@ -411,11 +378,10 @@ class InstallProgressController(SubiquityTuiController):
|
|||
os.remove(apt_conf.name)
|
||||
|
||||
async def stop_unattended_upgrades(self):
|
||||
self.progress_view.event_finish(self.unattended_upgrades_ctx)
|
||||
with self.unattended_upgrades_ctx.parent.child(
|
||||
"stop_unattended_upgrades",
|
||||
"cancelling update"):
|
||||
if self.opts.dry_run:
|
||||
if self.app.opts.dry_run:
|
||||
await asyncio.sleep(1)
|
||||
self.unattended_upgrades_proc.terminate()
|
||||
else:
|
||||
|
@ -426,22 +392,6 @@ class InstallProgressController(SubiquityTuiController):
|
|||
'--stop-only',
|
||||
]), check=True)
|
||||
|
||||
async def _click_reboot(self):
|
||||
if self.unattended_upgrades_ctx is not None:
|
||||
self.update_state(InstallState.UU_CANCELLING)
|
||||
await self.stop_unattended_upgrades()
|
||||
self.reboot_clicked.set()
|
||||
|
||||
def click_reboot(self):
|
||||
schedule_task(self._click_reboot())
|
||||
|
||||
def make_ui(self):
|
||||
schedule_task(self.move_on())
|
||||
return self.progress_view
|
||||
|
||||
def run_answers(self):
|
||||
pass
|
||||
|
||||
|
||||
uu_apt_conf = """\
|
||||
# Config for the unattended-upgrades run to avoid failing on battery power or
|
|
@ -111,7 +111,9 @@ class SubiquityServer(Application):
|
|||
|
||||
project = "subiquity"
|
||||
from subiquity.server import controllers as controllers_mod
|
||||
controllers = []
|
||||
controllers = [
|
||||
"Install",
|
||||
]
|
||||
|
||||
def make_model(self):
|
||||
root = '/'
|
||||
|
|
|
@ -3,15 +3,15 @@ from unittest import mock
|
|||
|
||||
from subiquitycore.testing import view_helpers
|
||||
|
||||
from subiquity.client.controllers.progress import ProgressController
|
||||
from subiquity.common.types import InstallState
|
||||
from subiquity.controllers.installprogress import InstallProgressController
|
||||
from subiquity.ui.views.installprogress import ProgressView
|
||||
|
||||
|
||||
class IdentityViewTests(unittest.TestCase):
|
||||
|
||||
def make_view(self):
|
||||
controller = mock.create_autospec(spec=InstallProgressController)
|
||||
controller = mock.create_autospec(spec=ProgressController)
|
||||
controller.app = mock.Mock()
|
||||
controller.app.aio_loop = None
|
||||
return ProgressView(controller)
|
||||
|
|
Loading…
Reference in New Issue