diff --git a/checkbox-ng/checkbox_ng/launcher/subcommands.py b/checkbox-ng/checkbox_ng/launcher/subcommands.py index b8e845f5ea..3636c4ba3b 100644 --- a/checkbox-ng/checkbox_ng/launcher/subcommands.py +++ b/checkbox-ng/checkbox_ng/launcher/subcommands.py @@ -1102,6 +1102,8 @@ def invoked(self, ctx): attrs = job_unit._raw_data.copy() attrs["full_id"] = job_unit.id attrs["id"] = job_unit.partial_id + attrs["certification_status"] = self.ctx.sa.get_job_state( + job).effective_certification_status jobs.append(attrs) if ctx.args.format == "?": all_keys = set() diff --git a/checkbox-ng/checkbox_ng/launcher/test_subcommands.py b/checkbox-ng/checkbox_ng/launcher/test_subcommands.py index 609e813941..e22257dae7 100644 --- a/checkbox-ng/checkbox_ng/launcher/test_subcommands.py +++ b/checkbox-ng/checkbox_ng/launcher/test_subcommands.py @@ -19,8 +19,8 @@ import unittest from unittest import TestCase from unittest.mock import patch, Mock - -from checkbox_ng.launcher.subcommands import Launcher +from io import StringIO +from checkbox_ng.launcher.subcommands import Launcher, ListBootstrapped class TestLauncher(TestCase): @@ -113,3 +113,100 @@ def test_invoke_returns_1_on_many_diff_outcomes(self): mock_results = {"fail": 6, "crash": 7, "pass": 8} self.ctx.sa.get_summary = Mock(return_value=mock_results) self.assertEqual(self.launcher.invoked(self.ctx), 1) + + +class TestLListBootstrapped(TestCase): + def setUp(self): + self.launcher = ListBootstrapped() + self.ctx = Mock() + self.ctx.args = Mock(TEST_PLAN="", format="") + self.ctx.sa = Mock( + start_new_session=Mock(), + get_test_plans=Mock( + return_value=["test-plan1", "test-plan2"]), + select_test_plan=Mock(), + bootstrap=Mock(), + get_static_todo_list=Mock( + return_value=["test-job1", "test-job2"]), + get_job=Mock( + side_effect=[ + Mock( + _raw_data={ + "id": "namespace1::test-job1", + "summary": "fake-job1", + "plugin": "manual", + "description": "fake-description1", + "certification_status": "unspecified" + }, + id="namespace1::test-job1", + partial_id="test-job1" + ), + Mock( + _raw_data={ + "id": "namespace2::test-job2", + "summary": "fake-job2", + "plugin": "shell", + "command": "ls", + "certification_status": "unspecified" + }, + id="namespace2::test-job2", + partial_id="test-job2" + ), + ] + ), + get_job_state=Mock( + return_value=Mock(effective_certification_status="blocker")), + get_resumable_sessions=Mock(return_value=[]), + get_dynamic_todo_list=Mock(return_value=[]), + ) + + def test_invoke_test_plan_not_found(self): + self.ctx.args.TEST_PLAN = "test-plan3" + + with self.assertRaisesRegex(SystemExit, "Test plan not found"): + self.launcher.invoked(self.ctx) + + @patch("sys.stdout", new_callable=StringIO) + def test_invoke_print_output_format(self, stdout): + self.ctx.args.TEST_PLAN = "test-plan1" + self.ctx.args.format = "?" + + expected_out = ( + "Available fields are:\ncertification_status, command, " + "description, full_id, id, plugin, summary\n" + ) + self.launcher.invoked(self.ctx) + self.assertEqual(stdout.getvalue(), expected_out) + + @patch("sys.stdout", new_callable=StringIO) + def test_invoke_print_output_standard_format(self, stdout): + self.ctx.args.TEST_PLAN = "test-plan1" + self.ctx.args.format = "{full_id}\n" + + expected_out = ( + "namespace1::test-job1\n" + "namespace2::test-job2\n" + ) + self.launcher.invoked(self.ctx) + self.assertEqual(stdout.getvalue(), expected_out) + + @patch("sys.stdout", new_callable=StringIO) + def test_invoke_print_output_customized_format(self, stdout): + self.ctx.args.TEST_PLAN = "test-plan1" + self.ctx.args.format = ( + "id: {id}\nplugin: {plugin}\nsummary: {summary}\n" + "certification blocker: {certification_status}\n\n" + ) + + expected_out = ( + "id: test-job1\n" + "plugin: manual\n" + "summary: fake-job1\n" + "certification blocker: blocker\n\n" + "id: test-job2\n" + "plugin: shell\n" + "summary: fake-job2\n" + "certification blocker: blocker\n\n" + ) + self.launcher.invoked(self.ctx) + self.assertEqual(stdout.getvalue(), expected_out)