import copy import datetime import json import tomllib import unittest from unittest import mock from tools import roadmap SAMPLE = '''[overview] today_plain = "a" today_dev = "b" help_plain = "c" help_dev = "d" [[area]] id = "performance" name = "Performance" # ---- Now [[item]] id = "fast" title = "Fast" status = "now" area = "performance" focus = true plain = "Faster." dev = """Multiply, \\ not divide (#10).""" links = ["#10"] [[item]] id = "faster" title = "Faster" status = "now" area = "performance" focus = true plain = "Even faster." dev = "More (#11)." links = ["#11", "#12"] [[item]] id = "fastest" title = "Fastest" status = "now" area = "performance" focus = true ongoing = true plain = "The fastest." dev = "Most (#13)." links = ["#13"] # ---- Shipped [[item]] id = "old" title = "Old" status = "shipped" shipped_in = "v0.2.1" area = "performance" plain = "Done." dev = "Done (#1)." links = ["#1"] ''' def pull(state, merged_at=None, paths=("crates/horizon-client/src/game.rs",)): return {"kind": "PullRequest", "state": state, "merged_at": merged_at, "merge_commit": "abc" if merged_at else None, "paths": list(paths)} def issue(state): return {"kind": "Issue", "state": state, "merged_at": None, "merge_commit": None, "paths": []} class RepositoryRoadmap(unittest.TestCase): def test_the_roadmap_keeps_its_rules(self): self.assertEqual(roadmap.validate(roadmap.load()), []) def test_generated_files_match_the_toml(self): for path, text in roadmap.outputs(roadmap.load()).items(): self.assertEqual(path.read_text(), text, f"{path.name} is out of date: run `python3 tools/roadmap.py build`") def test_site_data_has_every_item_in_one_line_text(self): data = json.loads(roadmap.site_data(tomllib.loads(SAMPLE))) self.assertEqual([item["id"] for item in data["items"]], ["fast", "faster", "fastest", "old"]) self.assertEqual(data["items"][0]["dev"], "Multiply, not divide (#10).") self.assertEqual(data["areas"], [{"id": "performance", "name": "Performance"}]) class Rules(unittest.TestCase): def setUp(self): self.data = tomllib.loads(SAMPLE) def problems(self, **changes): data = copy.deepcopy(self.data) data["item"][0].update(changes) return roadmap.validate(data) def test_the_sample_is_valid(self): self.assertEqual(roadmap.validate(self.data), []) def test_unknown_status_area_and_key(self): self.assertTrue(any("status" in p for p in self.problems(status="soon"))) self.assertTrue(any("area" in p for p in self.problems(area="nowhere"))) self.assertTrue(any("unknown key" in p for p in self.problems(eta="soon"))) def test_plain_fits_a_post(self): self.assertTrue(any("at most 240" in p for p in self.problems(plain="x" * 241))) self.assertTrue(any("post" in p for p in self.problems(post="x" * 241))) def test_shipped_needs_its_release(self): self.assertTrue(any("shipped_in" in p for p in self.problems(status="shipped", focus=False))) self.assertTrue(any("only shipped" in p for p in self.problems(shipped_in="v0.2.3"))) self.assertTrue(any("not a release" in p for p in self.problems(status="shipped", focus=False, shipped_in="soon"))) def test_links_are_issues_urls_or_repository_paths(self): self.assertEqual(self.problems(links=["#4", "https://playhorizon.dev", "docs/benchmark.md"]), []) self.assertTrue(any("link" in p for p in self.problems(links=["docs/nothing-here.md"]))) self.assertTrue(any("link" in p for p in self.problems(links=["http://example.com"]))) def test_ids_are_free_anchors_of_the_hub(self): for taken in ("roadmap", "now", "h-next", "tab-board"): self.assertTrue(any("taken by the hub page" in p for p in self.problems(id=taken)), taken) def test_flags_are_true_or_false(self): self.assertEqual(self.problems(announce=True), []) self.assertTrue(any("announce is true or false" in p for p in self.problems(announce="yes"))) def test_three_to_five_items_in_focus(self): self.assertTrue(any("focus" in p for p in self.problems(focus=False))) def test_no_dates(self): for text in ("Ready in March.", "Before 2027.", "In Q3.", "Next week.", "In 3 weeks.", "By the end of it."): self.assertTrue(roadmap.dated(text), text) for text in ("Coming soon.", "Pull request #2026.", "Java 26.3.", "Mobs march on.", "It may land."): self.assertIsNone(roadmap.dated(text), text) self.assertTrue(any("date" in p for p in self.problems(plain="Done in October."))) class Render(unittest.TestCase): def test_sections_links_and_the_overview(self): text = roadmap.render(tomllib.loads(SAMPLE)) self.assertIn("## Now", text) self.assertIn("## Recently shipped", text) self.assertNotIn("## Later", text) self.assertIn("- **Fast**: Faster.", text) self.assertIn("Multiply, not divide ([#10](https://github.com/horizon-rust/horizon/issues/10)).", text) # A link the technical text already gives isn't repeated; the others are listed after it. self.assertIn("More: [#12](https://github.com/horizon-rust/horizon/issues/12)", text) self.assertIn("- **Old** (v0.2.1): Done.", text) class GitHubState(unittest.TestCase): now = datetime.datetime(2030, 1, 20, tzinfo=datetime.timezone.utc) def test_a_now_item_without_live_pull_requests_is_stale(self): data = tomllib.loads(SAMPLE) states = {1: pull("MERGED", "2029-12-01T00:00:00Z"), 10: pull("OPEN"), 11: pull("MERGED", "2029-11-01T00:00:00Z"), 12: issue("OPEN"), 13: pull("MERGED", "2030-01-19T00:00:00Z")} problems, notes = roadmap.online_problems(data, states, self.now) self.assertEqual(len(problems), 1) self.assertIn("faster: stale", problems[0]) self.assertTrue(any("fastest: ongoing" in note for note in notes)) def test_missing_numbers_are_reported(self): problems, _ = roadmap.online_problems(tomllib.loads(SAMPLE), {}, self.now) self.assertTrue(any("#10 is neither" in p for p in problems)) class Journal(unittest.TestCase): def test_weekly_summaries_newest_first_with_their_monday(self): import tempfile from pathlib import Path with tempfile.TemporaryDirectory() as folder: folder = Path(folder) (folder / "2026-43.md").write_text("# A week\n\nPlain\nwords.\n\n## Technical\n\nDev (#1).\n") (folder / "2026-42.md").write_text("# Older\n\nPlain.\n\n## Technical\n\nDev.\n") (folder / "week-44.md").write_text("# Bad name\n") (folder / "2026-45.md").write_text("# No technical part\n\nPlain.\n") entries, problems = roadmap.weeks(folder) self.assertEqual([e["week"] for e in entries], ["2026-43", "2026-42"]) self.assertEqual(entries[0], {"week": "2026-43", "date": "2026-10-19", "title": "A week", "plain": "Plain words.", "dev": "Dev (#1)."}) self.assertEqual(len(problems), 2) class Labels(unittest.TestCase): def test_creates_updates_and_adds_labels(self): data = tomllib.loads(SAMPLE) existing = {"roadmap:fast": {"color": "5B3FD6", "description": "Roadmap, now: Fast"}, "roadmap:faster": {"color": "000000", "description": "old"}, "roadmap:gone": {"color": "000000", "description": ""}} states = {10: dict(pull("OPEN"), labels=["roadmap:fast"]), 11: dict(pull("OPEN"), labels=[]), 13: dict(pull("MERGED", "2030-01-01T00:00:00Z"), labels=[])} changes, stale = roadmap.label_changes(data, existing, states) self.assertEqual([(action, target) for action, target, _ in changes], [("update", "roadmap:faster"), ("add", 11), ("create", "roadmap:fastest"), ("create", "roadmap:old")]) self.assertEqual(changes[0][2]["color"], "5b3fd6") self.assertEqual(stale, ["roadmap:gone"]) class Sync(unittest.TestCase): def test_family_of_a_change(self): self.assertEqual(roadmap.family(["launcher/src/main.rs"]), "launcher") self.assertEqual(roadmap.family(["website/src/main.js"]), "website") self.assertEqual(roadmap.family(["launcher/src/main.rs", "crates/x/src/lib.rs"]), "game") def test_merged_and_released_items_move_to_the_top_of_shipped(self): data = tomllib.loads(SAMPLE) states = {10: pull("MERGED", "2030-01-02T00:00:00Z"), 11: pull("MERGED", "2030-01-03T00:00:00Z"), 12: issue("OPEN"), 13: pull("MERGED", "2030-01-03T00:00:00Z")} with mock.patch.object(roadmap, "first_release", return_value="v0.2.9") as release: text, log = roadmap.sync(data, SAMPLE, states) release.assert_called_once_with("abc", "game") # "faster" keeps an open issue and "fastest" is ongoing: only "fast" ships. self.assertEqual(log, ["fast: shipped in v0.2.9"]) new = tomllib.loads(text) self.assertEqual([item["id"] for item in new["item"]], ["faster", "fastest", "fast", "old"]) fast = new["item"][2] self.assertEqual((fast["status"], fast["shipped_in"]), ("shipped", "v0.2.9")) self.assertNotIn("focus", fast) self.assertEqual(fast["dev"], "Multiply, not divide (#10).") self.assertIn("# ---- Shipped\n\n[[item]]\nid = \"fast\"", text) def test_merged_but_unreleased_items_wait(self): states = {10: pull("MERGED", "2030-01-02T00:00:00Z")} with mock.patch.object(roadmap, "first_release", return_value=None): text, log = roadmap.sync(tomllib.loads(SAMPLE), SAMPLE, states) self.assertEqual(text, SAMPLE) self.assertEqual(log, ["fast: merged, waiting for a release"]) if __name__ == "__main__": unittest.main()