From 97440134d26a78695f7e25078769db8452834480 Mon Sep 17 00:00:00 2001 From: adityaanikam Date: Fri, 13 Nov 2026 03:34:30 +0530 Subject: [PATCH] fix(sleep): schedule forwards --scope to the scheduled run --- skillopt_sleep/__main__.py | 3 ++- skillopt_sleep/scheduler.py | 12 ++++++++---- tests/test_sleep_scheduler_safety.py | 18 ++++++++++++++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/skillopt_sleep/__main__.py b/skillopt_sleep/__main__.py index 6ac41c9a8..549193ee3 100644 --- a/skillopt_sleep/__main__.py +++ b/skillopt_sleep/__main__.py @@ -838,7 +838,8 @@ def cmd_schedule(args) -> int: project = cfg.get("invoked_project") or os.getcwd() ok, msg = schedule(project, backend=cfg.get("backend", "mock"), hour=args.hour, minute=args.minute, - extra=("--auto-adopt" if getattr(args, "auto_adopt", False) else "")) + extra=("--auto-adopt" if getattr(args, "auto_adopt", False) else ""), + scope=args.scope or "invoked") print("[sleep] " + _display_value(msg)) cur = list_scheduled() if cur: diff --git a/skillopt_sleep/scheduler.py b/skillopt_sleep/scheduler.py index 44dfa4b89..9da3db0b0 100644 --- a/skillopt_sleep/scheduler.py +++ b/skillopt_sleep/scheduler.py @@ -103,7 +103,10 @@ def _list_win_tasks() -> List[str]: return [] -def _runner_cmd(project: str, backend: str, extra: str, python: str) -> str: +def _runner_cmd(project: str, backend: str, extra: str, python: str, + scope: str = "invoked") -> str: + if scope not in ("invoked", "all"): + raise ValueError(f"scope must be 'invoked' or 'all', got {scope!r}") _validate_scheduler_text("project", project) _validate_scheduler_text("python", python) _validate_scheduler_text("repository", _repo_root()) @@ -120,7 +123,7 @@ def _runner_cmd(project: str, backend: str, extra: str, python: str) -> str: "--project", project, "--scope", - "invoked", + scope, "--backend", backend, *shlex.split(extra), @@ -184,7 +187,8 @@ def _project_marker(project: str) -> str: def schedule(project: str, *, backend: str = "mock", hour: int = 3, minute: int = 17, - extra: str = "", python: Optional[str] = None) -> Tuple[bool, str]: + extra: str = "", python: Optional[str] = None, + scope: str = "invoked") -> Tuple[bool, str]: """Install (or replace) the nightly entry for ``project``. Returns (installed, message). If the scheduler backend is unavailable, installed=False and @@ -197,7 +201,7 @@ def schedule(project: str, *, backend: str = "mock", hour: int = 3, minute: int project = os.path.abspath(project) python = python or sys.executable or "python3" try: - runner_cmd = _runner_cmd(project, backend, extra, python) + runner_cmd = _runner_cmd(project, backend, extra, python, scope) except (ValueError, OSError) as exc: return False, f"Refusing unsafe scheduler configuration: {exc}" diff --git a/tests/test_sleep_scheduler_safety.py b/tests/test_sleep_scheduler_safety.py index 992b19cdc..f4d4a0f88 100644 --- a/tests/test_sleep_scheduler_safety.py +++ b/tests/test_sleep_scheduler_safety.py @@ -25,6 +25,24 @@ def test_posix_runner_quotes_every_path_and_argument(self): self.assertIn(shlex.quote("/tmp/python with spaces"), command) self.assertNotIn("$(", command) + @unittest.skipIf(os.name == "nt", "POSIX runner quoting is not used on Windows") + def test_scheduled_run_uses_the_requested_scope(self): + with tempfile.TemporaryDirectory() as project: + default = scheduler._runner_cmd(project, "mock", "", "python3") + invoked = scheduler._runner_cmd(project, "mock", "", "python3", "invoked") + everything = scheduler._runner_cmd(project, "mock", "", "python3", "all") + self.assertEqual(default, invoked) + for command, expected in ((default, "invoked"), (everything, "all")): + tokens = shlex.split(command) + self.assertEqual(tokens[tokens.index("--scope") + 1], expected) + + def test_unknown_scope_is_refused_before_crontab_write(self): + with mock.patch.object(scheduler, "_write_crontab") as write: + ok, message = scheduler.schedule("/tmp/project", scope="everything") + self.assertFalse(ok) + self.assertIn("scope must be", message) + write.assert_not_called() + def test_control_characters_are_refused_before_crontab_write(self): with mock.patch.object(scheduler, "_write_crontab") as write: ok, message = scheduler.schedule("/tmp/project\n* * * * * injected")