diff --git a/scripts/profile_sync.py b/scripts/profile_sync.py index 580f677b..d2796098 100644 --- a/scripts/profile_sync.py +++ b/scripts/profile_sync.py @@ -3027,6 +3027,31 @@ def main() -> None: file=sys.stderr, ) raise SystemExit(1) + machine = [ + flag for flag, on in ( + ("--json", args.as_json), + ("--markdown", args.markdown), + ("--fetch-plan", args.fetch_plan), + ("--triage", args.triage), + ) if on + ] + extras = [ + flag for flag, on in ( + ("--check-version", args.check_version), + ("--detect-new-files", args.detect_new_files), + ("--watch-hashes", args.watch_hashes), + ("--full-diff", args.full_diff), + ("--tree-diff", args.tree_diff), + ) if on + ] + if machine and extras: + # The extra sections only exist in the text report; the other outputs + # returned before reaching them and printed as if never asked. + print( + f"{', '.join(machine)} does not carry {', '.join(extras)}", + file=sys.stderr, + ) + raise SystemExit(1) profiles = load_emulator_profiles(args.emulators_dir, skip_aliases=False) selected = select_profiles(profiles, args) _check_quota(len(selected), args.offline) @@ -3094,22 +3119,28 @@ def main() -> None: # The guard already left the file untouched; keep going. print(f"{name}: write refused: {exc}", file=sys.stderr) + # --changed-only narrows every output, not the text report alone: the + # documented `--all --changed-only --json` listed every profile. + shown = [ + report for report in reports + if not args.changed_only or report.needs_review() + ] if args.as_json: - print(json.dumps([report_to_dict(r) for r in reports], indent=2)) + print(json.dumps([report_to_dict(r) for r in shown], indent=2)) return if args.markdown: target = Path(args.report_dir) / f"profile-sync-{date.today().isoformat()}.md" target.parent.mkdir(parents=True, exist_ok=True) - target.write_text(format_markdown(reports), encoding="utf-8") + target.write_text(format_markdown(shown), encoding="utf-8") print(f"written: {target}") return if args.fetch_plan: - for report in reports: + for report in shown: for url in fetch_plan(report): print(url) return if args.triage: - _print_triage(args, selected, reports) + _print_triage(args, selected, shown) return printed = 0 for report in reports: diff --git a/tests/test_profile_sync.py b/tests/test_profile_sync.py index 67bb1051..4ffc130e 100644 --- a/tests/test_profile_sync.py +++ b/tests/test_profile_sync.py @@ -3293,6 +3293,46 @@ class TestRealignProse(unittest.TestCase): self.assertIn("(deep.c:14)", self.path.read_text()) +class TestOutputModesApplyTheirFlags(unittest.TestCase): + """--changed-only narrows --json too; extras are refused off the text report.""" + + def _main(self, *argv): + from unittest import mock + + clean = ProfileReport(name="clean") + drift = ProfileReport(name="drift", counts={"CHANGED": 1}) + reports = {"clean": clean, "drift": drift} + out, err = io.StringIO(), io.StringIO() + saved = sys.argv + try: + sys.argv = ["profile_sync.py", "--all", *argv] + with mock.patch.object(profile_sync, "load_emulator_profiles", return_value={}), \ + mock.patch.object(profile_sync, "select_profiles", + return_value={"clean": {}, "drift": {}}), \ + mock.patch.object(profile_sync, "_check_quota"), \ + mock.patch.object(profile_sync, "build_report", + side_effect=lambda name, *a, **k: reports[name]), \ + contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): + try: + profile_sync.main() + code = 0 + except SystemExit as exc: + code = exc.code + finally: + sys.argv = saved + return code, out.getvalue(), err.getvalue() + + def test_changed_only_narrows_json(self): + code, out, _ = self._main("--changed-only", "--json") + self.assertEqual(code, 0) + self.assertEqual([r["name"] for r in json.loads(out)], ["drift"]) + + def test_extras_are_refused_with_json(self): + code, _, err = self._main("--json", "--check-version") + self.assertEqual(code, 1) + self.assertIn("--check-version", err) + + class TestRealignFlagMatrix(unittest.TestCase): """--realign-prose applies a flag or refuses it, never swallows it."""