Skip to content

Commit a108916

Browse files
Hook25pieqq
andauthored
Fix clear old sessions for local (BugFix) (canonical#1708)
* Fix top level args parsing in checkbox local Minor: this fixes them all, including the debug level * Minor: remove validator as it is wrong last_job_start_time is mostly None as whenever a job finishes it will be none * Test the fixed clear-old-sessions message Minor: also fix metabox as cmd was overwritten when a manifest was provided * Fix the crash in remote as well * Fix and add new unittest * Test also args * Also test the utils function * Python3.5 :( * Forgotten tool to corrupt sessions * Implement feedback * python3.5 :( * Fix interactive comand result fetching (infra) (canonical#1734) * Result fetching for run_cmd * Give the result half a second to materialize * Add result to the ExecuteResult * Preserve all fields on check * Wrong check called * Check is not a property * Trace metabox output * Rollback the binary load to test, make result array * Set the cmd to none in non-interactive for now * Revert trace logging * Purge debug files * Remove pointless append * Use reference in the url comment Co-authored-by: Pierre Equoy <pierre.equoy@canonical.com> --------- Co-authored-by: Pierre Equoy <pierre.equoy@canonical.com> --------- Co-authored-by: Pierre Equoy <pierre.equoy@canonical.com>
1 parent 1a56005 commit a108916

14 files changed

Lines changed: 393 additions & 72 deletions

File tree

checkbox-ng/checkbox_ng/launcher/checkbox_cli.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@
2222

2323
import gettext
2424
import logging
25-
import os
26-
import subprocess
2725
import sys
2826

27+
import checkbox_ng
28+
2929
from plainbox.impl.jobcache import ResourceJobCache
3030
from plainbox.impl.session.assistant import SessionAssistant
3131

32-
from checkbox_ng.config import load_configs
32+
from checkbox_ng.utils import set_all_loggers_level
3333
from checkbox_ng.launcher.subcommands import (
3434
Launcher,
3535
List,
@@ -62,6 +62,35 @@ def reset_sa(self):
6262
self.sa = SessionAssistant()
6363

6464

65+
def handle_top_parser(args, ctx):
66+
"""
67+
The top level parser may not contain all args as "launcher" is inserted
68+
to get a default command. Lets handle the args as stings here and unify the
69+
args (from the top level parser) and ctx.args (from the sub parser)
70+
"""
71+
if "--debug" in sys.argv:
72+
logging_level = logging.DEBUG
73+
logging.basicConfig(level=logging_level)
74+
set_all_loggers_level(logging.DEBUG)
75+
ctx.args.debug = True
76+
elif "--verbose" in sys.argv or "-v" in sys.argv:
77+
logging_level = logging.INFO
78+
logging.basicConfig(level=logging_level)
79+
set_all_loggers_level(logging.INFO)
80+
ctx.args.verbose = True
81+
if "--clear-cache" in sys.argv:
82+
ResourceJobCache().clear()
83+
ctx.args.clear_cache = True
84+
if "--clear-old-sessions" in sys.argv:
85+
old_sessions = [s[0] for s in ctx.sa.get_old_sessions()]
86+
ctx.sa.delete_sessions(old_sessions)
87+
ctx.args.clear_old_sessions = True
88+
if "--version" in sys.argv:
89+
print(checkbox_ng.__version__)
90+
raise SystemExit(0)
91+
return ctx
92+
93+
6594
def main():
6695
import argparse
6796

@@ -105,6 +134,7 @@ def main():
105134
)
106135

107136
top_parser = argparse.ArgumentParser()
137+
# You must handle these args in the function above, see docstring
108138
top_parser.add_argument(
109139
"-v",
110140
"--verbose",
@@ -148,21 +178,7 @@ def main():
148178
sub_args = subcmd_parser.parse_args(sys.argv[subcmd_index + 1 :])
149179
sa = SessionAssistant()
150180
ctx = Context(sub_args, sa)
151-
try:
152-
socket.getaddrinfo("localhost", 443) # 443 for HTTPS
153-
except Exception:
154-
pass
155-
if "--clear-cache" in sys.argv:
156-
ResourceJobCache().clear()
157-
if "--clear-old-sessions" in sys.argv:
158-
old_sessions = [s[0] for s in sa.get_old_sessions()]
159-
sa.delete_sessions(old_sessions)
160-
if args.verbose:
161-
logging_level = logging.INFO
162-
logging.basicConfig(level=logging_level)
163-
if args.debug:
164-
logging_level = logging.DEBUG
165-
logging.basicConfig(level=logging_level)
181+
ctx = handle_top_parser(args, ctx)
166182
return subcmd.invoked(ctx)
167183

168184

checkbox-ng/checkbox_ng/launcher/controller.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@
4141
from plainbox.impl.result import MemoryJobResult
4242
from plainbox.impl.color import Colorizer
4343
from plainbox.impl.config import Configuration
44-
from plainbox.impl.session.resume import IncompatibleJobError
44+
from plainbox.impl.session.resume import (
45+
IncompatibleJobError,
46+
CorruptedSessionError,
47+
)
4548
from plainbox.impl.session.remote_assistant import RemoteSessionAssistant
4649
from plainbox.vendor import rpyc
4750
from checkbox_ng.resume_menu import ResumeMenu
@@ -384,6 +387,8 @@ def _resumed_session(self, session_id):
384387
# so that it can be treated as a normal "local" exception"
385388
if "plainbox.impl.session.resume.IncompatibleJobError" in str(e):
386389
raise IncompatibleJobError(*e.args)
390+
if "plainbox.impl.session.resume.CorruptedSessionError" in str(e):
391+
raise CorruptedSessionError(*e.args)
387392
raise
388393
finally:
389394
self.sa.abandon_session()
@@ -408,9 +413,9 @@ def should_start_via_autoresume(self) -> bool:
408413
# FIXME: IncompatibleJobError is raised if the resume candidate is
409414
# invalid, this is a workaround till get_resumable_sessions is
410415
# fixed
411-
with contextlib.suppress(IncompatibleJobError), self._resumed_session(
412-
last_abandoned_session.id
413-
) as metadata:
416+
with contextlib.suppress(IncompatibleJobError), contextlib.suppress(
417+
CorruptedSessionError
418+
), self._resumed_session(last_abandoned_session.id) as metadata:
414419
app_blob = json.loads(metadata.app_blob.decode("UTF-8"))
415420

416421
if not app_blob.get("testplan_id"):

checkbox-ng/checkbox_ng/launcher/subcommands.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@
4040

4141
from plainbox.abc import IJobResult
4242
from plainbox.impl.color import Colorizer
43-
from plainbox.impl.session.resume import IncompatibleJobError
43+
from plainbox.impl.session.resume import (
44+
IncompatibleJobError,
45+
CorruptedSessionError,
46+
)
4447
from plainbox.impl.execution import UnifiedRunner
4548
from plainbox.impl.highlevel import Explorer
4649
from plainbox.impl.result import MemoryJobResult
@@ -378,7 +381,7 @@ def _should_autoresume_last_run(self, resume_candidates):
378381
if job_state.job.plugin != "shell":
379382
return False
380383
return True
381-
except IncompatibleJobError as ije:
384+
except (CorruptedSessionError, IncompatibleJobError) as ije:
382385
# last resumable session is incompatible, produce a helpful log
383386
_logger.error(
384387
"Checkbox tried to resume last session (%s), but the "
@@ -420,6 +423,8 @@ def _auto_resume_session(self, resume_candidates):
420423
return True
421424
else:
422425
raise RuntimeError("Requested session is not resumable!")
426+
elif self.ctx.args.clear_old_sessions:
427+
return False
423428
elif self._should_autoresume_last_run(resume_candidates):
424429
last_session = resume_candidates[0]
425430
self._resume_session(last_session.id, None)

checkbox-ng/checkbox_ng/launcher/test_checkbox_cli.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
from collections import namedtuple
2020
from unittest import TestCase, mock
2121

22-
from checkbox_ng.launcher.checkbox_cli import main
22+
from checkbox_ng.launcher.checkbox_cli import (
23+
main,
24+
handle_top_parser,
25+
)
2326

2427

2528
class CheckboxCliTests(TestCase):
@@ -47,3 +50,46 @@ def test_launcher_ok(
4750

4851
self.assertTrue(launcher_mock.called)
4952
self.assertTrue(launcher_mock.invoked.called)
53+
54+
55+
@mock.patch("checkbox_ng.launcher.checkbox_cli.logging", new=mock.MagicMock())
56+
class TestHandleTopParser(TestCase):
57+
58+
@mock.patch("sys.argv", ["--debug"])
59+
@mock.patch("checkbox_ng.launcher.checkbox_cli.set_all_loggers_level")
60+
def test_debug_flag(self, set_all_loggers_level_mock):
61+
ctx = mock.MagicMock()
62+
result = handle_top_parser(None, ctx)
63+
self.assertTrue(result.args.debug)
64+
self.assertTrue(set_all_loggers_level_mock.called)
65+
66+
@mock.patch("sys.argv", ["--verbose"])
67+
@mock.patch("checkbox_ng.launcher.checkbox_cli.set_all_loggers_level")
68+
def test_verbose_flag(self, set_all_loggers_level_mock):
69+
ctx = mock.MagicMock()
70+
result = handle_top_parser(None, ctx)
71+
self.assertTrue(result.args.verbose)
72+
self.assertTrue(set_all_loggers_level_mock.called)
73+
74+
@mock.patch("sys.argv", ["--clear-cache"])
75+
@mock.patch("checkbox_ng.launcher.checkbox_cli.ResourceJobCache")
76+
def test_clear_cache(self, mock_cache):
77+
ctx = mock.MagicMock()
78+
result = handle_top_parser(None, ctx)
79+
self.assertTrue(mock_cache().clear.called)
80+
self.assertTrue(result.args.clear_cache)
81+
82+
@mock.patch("sys.argv", ["--clear-old-sessions"])
83+
def test_clear_old_sessions(self):
84+
ctx = mock.MagicMock()
85+
ctx.sa.get_old_sessions.return_value = [("session1",)]
86+
result = handle_top_parser(None, ctx)
87+
ctx.sa.delete_sessions.assert_called_with(["session1"])
88+
self.assertTrue(result.args.clear_old_sessions)
89+
90+
@mock.patch("sys.argv", ["--version"])
91+
def test_version_flag(self):
92+
ctx = mock.MagicMock()
93+
with self.assertRaises(SystemExit) as cm:
94+
handle_top_parser(None, ctx)
95+
self.assertEqual(cm.exception.code, 0)

checkbox-ng/checkbox_ng/launcher/test_subcommands.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -724,14 +724,30 @@ def test__auto_resume_session_from_ctx_unknown_session(self):
724724
def test__auto_resume_session_autoresume(self):
725725
self_mock = MagicMock()
726726
resume_candidate_mock = MagicMock(id="session_to_resume")
727+
# session id wasn't provided directly via the cli
727728
self_mock.ctx.args.session_id = None
729+
# --clear-old-sessions wasn't used
730+
self_mock.ctx.args.clear_old_sessions = False
728731
self_mock._should_autoresume_last_run.return_value = True
729732

730733
self.assertTrue(
731734
Launcher._auto_resume_session(self_mock, [resume_candidate_mock])
732735
)
733736
self.assertTrue(self_mock._resume_session.called)
734737

738+
def test__auto_resume_session_no_autoresume_on_clear(self):
739+
self_mock = MagicMock()
740+
resume_candidate_mock = MagicMock(id="session_to_resume")
741+
# session id wasn't provided directly via the cli
742+
self_mock.ctx.args.session_id = None
743+
# --clear-old-sessions was used, so we don't autoresume
744+
self_mock.ctx.args.clear_old_sessions = True
745+
746+
self.assertFalse(
747+
Launcher._auto_resume_session(self_mock, [resume_candidate_mock])
748+
)
749+
self.assertFalse(self_mock._resume_session.called)
750+
735751
def test__auto_resume_session_no_autoresume(self):
736752
self_mock = MagicMock()
737753
resume_candidate_mock = MagicMock(id="session_to_resume")

checkbox-ng/checkbox_ng/test_utils.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
# along with Checkbox. If not, see <http://www.gnu.org/licenses/>.
1818

1919
import unittest
20-
from checkbox_ng.utils import newline_join
20+
from checkbox_ng.utils import newline_join, set_all_loggers_level
2121

2222
"""Tests for checkbox_ng.utils"""
2323

@@ -43,3 +43,27 @@ def test_newline_join_with_single_argument(self):
4343
def test_newline_join_with_no_arguments(self):
4444
expected_result = ""
4545
self.assertEqual(newline_join(""), expected_result)
46+
47+
48+
class TestSetAllLoggersLevel(unittest.TestCase):
49+
@unittest.mock.patch("checkbox_ng.utils.logging")
50+
def test_set_all_loggers_level(self, logging_mock):
51+
class FakeLogger:
52+
def __init__(self, name, level):
53+
self.name = name
54+
self.level = level
55+
56+
def setLevel(self, level):
57+
self.level = level
58+
59+
logging_mock.root.manager.loggerDict = {
60+
"some": FakeLogger("some", 10),
61+
"other": FakeLogger("other", 230),
62+
}
63+
set_all_loggers_level(0)
64+
self.assertTrue(
65+
all(
66+
x.level == 0
67+
for x in logging_mock.root.manager.loggerDict.values()
68+
)
69+
)

checkbox-ng/checkbox_ng/utils.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,25 @@
2020
Generic utility functions.
2121
"""
2222
import json
23+
import logging
2324
import textwrap
2425
import datetime
2526

2627
from plainbox.impl.color import Colorizer
2728

2829

30+
def set_all_loggers_level(level):
31+
"""
32+
Sets the level of loggers already instantiated
33+
"""
34+
logging.root.setLevel(level)
35+
for logger in logging.root.manager.loggerDict.values():
36+
try:
37+
logger.setLevel(level)
38+
except AttributeError:
39+
pass
40+
41+
2942
def newline_join(head: str, *tail: str) -> str:
3043
"""
3144
Join strings with newlines.

checkbox-ng/plainbox/impl/session/resume.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ def _restore_SessionState_metadata(cls, metadata, session_repr):
491491
session_repr["metadata"],
492492
key="last_job_start_time",
493493
value_type=float,
494+
value_none=True,
494495
)
495496

496497

0 commit comments

Comments
 (0)