Skip to content

Commit

Permalink
Fix quit option for manual jobs (bugfix) (canonical#989)
Browse files Browse the repository at this point in the history
* Fix quit

* Add new ui quit metabox test

Minor: renamed urwidui -> ui

* Test RemoteController change

* Test also remote assistant remember_users_response
  • Loading branch information
Hook25 authored and LiaoU3 committed Mar 20, 2024
1 parent 8524259 commit 228ba9c
Show file tree
Hide file tree
Showing 7 changed files with 244 additions and 1 deletion.
6 changes: 6 additions & 0 deletions checkbox-ng/checkbox_ng/launcher/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,9 @@ def _run_jobs(self, jobs_repr, total_num=0):
)
if cmd == "skip":
next_job = True
elif cmd == "quit":
self.sa.remember_users_response(cmd)
raise SystemExit("Session saved, exiting...")
self.sa.remember_users_response(cmd)
self.wait_for_job(dont_finish=True)
elif interaction.kind in "steps":
Expand All @@ -738,6 +741,9 @@ def _run_jobs(self, jobs_repr, total_num=0):
)
if cmd == "skip":
next_job = True
elif cmd == "quit":
self.sa.remember_users_response(cmd)
raise SystemExit("Session saved, exiting...")
self.sa.remember_users_response(cmd)
elif interaction.kind == "verification":
self.wait_for_job(dont_finish=True)
Expand Down
148 changes: 147 additions & 1 deletion checkbox-ng/checkbox_ng/launcher/test_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,152 @@ def test_resume_or_start_new_session_interactive(self):

self.assertTrue(self_mock.interactively_choose_tp.called)

@mock.patch("checkbox_ng.launcher.controller.SimpleUI")
def test__run_jobs_description_command_none(self, simple_ui_mock):
self_mock = mock.MagicMock()
interaction_mock = mock.MagicMock()
interaction_mock.kind = "description"

self_mock.sa.run_job.return_value = [interaction_mock]
jobs_repr_mock = {
"id": "id_mock",
"command": None,
"num": 0,
"name": "name",
"category_name": "category",
}
simple_ui_mock().wait_for_interaction_prompt.return_value = "skip"

RemoteController._run_jobs(self_mock, [jobs_repr_mock])

@mock.patch("checkbox_ng.launcher.controller.SimpleUI")
def test__run_jobs_description_skip(self, simple_ui_mock):
self_mock = mock.MagicMock()
interaction_mock = mock.MagicMock()
interaction_mock.kind = "description"

self_mock.sa.run_job.return_value = [interaction_mock]
jobs_repr_mock = {
"id": "id_mock",
"command": "skip_description",
"num": 0,
"name": "name",
"category_name": "category",
}
simple_ui_mock().wait_for_interaction_prompt.return_value = "skip"

RemoteController._run_jobs(self_mock, [jobs_repr_mock])

@mock.patch("checkbox_ng.launcher.controller.SimpleUI")
def test__run_jobs_description_enter(self, simple_ui_mock):
self_mock = mock.MagicMock()
interaction_mock = mock.MagicMock()
interaction_mock.kind = "description"

self_mock.sa.run_job.return_value = [interaction_mock]
jobs_repr_mock = {
"id": "id_mock",
"command": "skip_description",
"num": 0,
"name": "name",
"category_name": "category",
}
simple_ui_mock().wait_for_interaction_prompt.return_value = ""

RemoteController._run_jobs(self_mock, [jobs_repr_mock])

@mock.patch("checkbox_ng.launcher.controller.SimpleUI")
def test__run_jobs_description_quit(self, simple_ui_mock):
self_mock = mock.MagicMock()
interaction_mock = mock.MagicMock()
interaction_mock.kind = "description"

self_mock.sa.run_job.return_value = [interaction_mock]
jobs_repr_mock = {
"id": "id_mock",
"command": "quit_description",
"num": 0,
"name": "name",
"category_name": "category",
}
simple_ui_mock().wait_for_interaction_prompt.return_value = "quit"

with self.assertRaises(SystemExit):
RemoteController._run_jobs(self_mock, [jobs_repr_mock])

@mock.patch("checkbox_ng.launcher.controller.SimpleUI")
def test__run_jobs_steps_run(self, simple_ui_mock):
self_mock = mock.MagicMock()
interaction_mock = mock.MagicMock()
interaction_mock.kind = "steps"

self_mock.sa.run_job.return_value = [interaction_mock]
jobs_repr_mock = {
"id": "id_mock",
"command": None,
"num": 0,
"name": "name",
"category_name": "category",
}

RemoteController._run_jobs(self_mock, [jobs_repr_mock])

@mock.patch("checkbox_ng.launcher.controller.SimpleUI")
def test__run_jobs_steps_enter(self, simple_ui_mock):
self_mock = mock.MagicMock()
interaction_mock = mock.MagicMock()
interaction_mock.kind = "steps"

self_mock.sa.run_job.return_value = [interaction_mock]
jobs_repr_mock = {
"id": "id_mock",
"command": "skip_description",
"num": 0,
"name": "name",
"category_name": "category",
}
simple_ui_mock().wait_for_interaction_prompt.return_value = ""

RemoteController._run_jobs(self_mock, [jobs_repr_mock])

@mock.patch("checkbox_ng.launcher.controller.SimpleUI")
def test__run_jobs_steps_skip(self, simple_ui_mock):
self_mock = mock.MagicMock()
interaction_mock = mock.MagicMock()
interaction_mock.kind = "steps"

self_mock.sa.run_job.return_value = [interaction_mock]
jobs_repr_mock = {
"id": "id_mock",
"command": "skip_description",
"num": 0,
"name": "name",
"category_name": "category",
}
simple_ui_mock().wait_for_interaction_prompt.return_value = "skip"

RemoteController._run_jobs(self_mock, [jobs_repr_mock])

@mock.patch("checkbox_ng.launcher.controller.SimpleUI")
def test__run_jobs_steps_quit(self, simple_ui_mock):
self_mock = mock.MagicMock()
interaction_mock = mock.MagicMock()
interaction_mock.kind = "steps"

self_mock.sa.run_job.return_value = [interaction_mock]
jobs_repr_mock = {
"id": "id_mock",
"command": "quit_description",
"num": 0,
"name": "name",
"category_name": "category",
}
simple_ui_mock().wait_for_interaction_prompt.return_value = "quit"

with self.assertRaises(SystemExit):
RemoteController._run_jobs(self_mock, [jobs_repr_mock])


class IsHostnameALoopbackTests(TestCase):
@mock.patch("socket.gethostbyname")
@mock.patch("ipaddress.ip_address")
Expand Down Expand Up @@ -294,4 +440,4 @@ def test_is_hostname_a_loopback_socket_raises(self, gethostbyname_mock):
when the socket.gethostname function raises an exception
"""
gethostbyname_mock.side_effect = socket.gaierror
self.assertFalse(is_hostname_a_loopback("foobar"))
self.assertFalse(is_hostname_a_loopback("foobar"))
5 changes: 5 additions & 0 deletions checkbox-ng/plainbox/impl/session/remote_assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,11 @@ def remember_users_response(self, response):
self._current_comments = ""
self._state = TestsSelected
return
elif response == "quit":
self._last_response = response
self._state = Idle
self.finalize_session()
return
self._last_response = response
self._state = Running

Expand Down
30 changes: 30 additions & 0 deletions checkbox-ng/plainbox/impl/session/test_remote_assistant.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,36 @@ def test_resume_by_id_with_result_file_not_json(self, mock_load_configs):

rsa._sa.use_job_result.assert_called_with(rsa._last_job, mjr, True)

def test_remember_users_response_quit(self):
self_mock = mock.MagicMock()
self_mock._state = remote_assistant.Interacting

remote_assistant.RemoteSessionAssistant.remember_users_response(
self_mock, "quit"
)

self.assertEqual(self_mock._state, remote_assistant.Idle)
self.assertTrue(self_mock.finalize_session.called)

def test_remember_users_response_rollback(self):
self_mock = mock.MagicMock()
self_mock._state = remote_assistant.Interacting

remote_assistant.RemoteSessionAssistant.remember_users_response(
self_mock, "rollback"
)

self.assertEqual(self_mock._state, remote_assistant.TestsSelected)

def test_remember_users_response_run(self):
self_mock = mock.MagicMock()
self_mock._state = remote_assistant.Interacting

remote_assistant.RemoteSessionAssistant.remember_users_response(
self_mock, "run"
)

self.assertEqual(self_mock._state, remote_assistant.Running)

class RemoteAssistantFinishJobTests(TestCase):
def setUp(self):
Expand Down
File renamed without changes.
56 changes: 56 additions & 0 deletions metabox/metabox/scenarios/ui/interact_jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# This file is part of Checkbox.
#
# Copyright 2024 Canonical Ltd.
# Written by:
# Massimiliano Girardi <[email protected]>
#
# Checkbox is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3,
# as published by the Free Software Foundation.

#
# Checkbox 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 Checkbox. If not, see <http://www.gnu.org/licenses/>.
import textwrap

import metabox.core.keys as keys
from metabox.core.actions import (
Expect,
Send,
Start,
ExpectNot
)
from metabox.core.scenario import Scenario
from metabox.core.utils import tag


@tag("manual", "interact")
class ManualInteractQuit(Scenario):
launcher = textwrap.dedent(
"""
[launcher]
launcher_version = 1
stock_reports = text
[test plan]
unit = 2021.com.canonical.certification::cert-blocker-manual-resume
forced = yes
[test selection]
forced = yes
"""
)

steps = [
Start(),
Expect("Pick an action"),
Send("p" + keys.KEY_ENTER),
Expect("save the session and quit"),
Send("q" + keys.KEY_ENTER),
# if q is pressed, checkbox should exit instead of going ahead printing
# results
ExpectNot("Results")
]
File renamed without changes.

0 comments on commit 228ba9c

Please sign in to comment.