d53a366718
- First run (and when CONSENT_VERSION changes): a question over the launcher with four switches, all off, what each sends in plain words, what is never sent, "View what would be sent" (the exact JSON), and "Send nothing" / "Save choices". - Settings → Data collection: the same switches any time, the install ID and what waits, "View what would be sent" and "Delete my data" (server deletion, outbox cleared, new install ID). - The outbox is sent at start and on close (at most 3 s), only when there is a server (none yet) and only what the player allows. - CLI `horizon-launcher data status|allow|deny|show|delete|send|bug|benchmark` and MCP tools data_collection, set_data_collection, data_preview, delete_my_data, report_bug. Bug reports and benchmark uploads are printed for the player to confirm; logs are scrubbed and lose chat lines. - horizon-telemetry joins the launcher's paths (launcher.yml, release_train, pr_ready). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
118 lines
7.7 KiB
Python
118 lines
7.7 KiB
Python
import argparse
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from tools import pr_ready
|
|
|
|
PACKAGES = {"horizon-core": "crates/horizon-core", "horizon-client": "crates/horizon-client",
|
|
"horizon-theme": "crates/horizon-theme", "xtask": "xtask"}
|
|
|
|
|
|
def args(host="local", quick=False):
|
|
return argparse.Namespace(host=host, quick=quick)
|
|
|
|
|
|
class PrReadyTests(unittest.TestCase):
|
|
def test_paths_map_to_the_crates_that_own_them(self):
|
|
self.assertEqual(pr_ready.touched_crates({"crates/horizon-core/src/jmath.rs", "crates/horizon-client/build.rs",
|
|
"xtask/src/main.rs", "tools/parity.py"}, PACKAGES),
|
|
["horizon-client", "horizon-core", "xtask"])
|
|
self.assertEqual(pr_ready.touched_crates({"crates/horizon-core/README.md"}, PACKAGES), [])
|
|
# The generated stylesheet is checked by horizon-theme's tests.
|
|
self.assertEqual(pr_ready.touched_crates({"website/src/tokens.css"}, PACKAGES), ["horizon-theme"])
|
|
# A crate whose name prefixes another's directory doesn't own it.
|
|
self.assertEqual(pr_ready.touched_crates({"crates/horizon-core-extra/x.rs"}, PACKAGES), [])
|
|
# The workspace manifest and lock touch every crate.
|
|
self.assertIsNone(pr_ready.touched_crates({"Cargo.lock", "crates/horizon-core/src/lib.rs"}, PACKAGES))
|
|
|
|
def test_which_workspace_builds_a_path(self):
|
|
self.assertTrue(pr_ready.workspace_rust("crates/horizon-theme/fonts/x.ttf"))
|
|
self.assertTrue(pr_ready.workspace_rust("Cargo.toml"))
|
|
self.assertFalse(pr_ready.workspace_rust("crates/horizon-client/README.md"))
|
|
self.assertFalse(pr_ready.workspace_rust("launcher/src/main.rs"))
|
|
self.assertTrue(pr_ready.launcher_rust("launcher/src/main.rs"))
|
|
self.assertTrue(pr_ready.launcher_rust("crates/horizon-prepare/src/lib.rs"))
|
|
self.assertTrue(pr_ready.launcher_rust("crates/horizon-theme/src/lib.rs"))
|
|
self.assertTrue(pr_ready.launcher_rust("crates/horizon-telemetry/src/lib.rs"))
|
|
self.assertFalse(pr_ready.launcher_rust("launcher/links.json"))
|
|
self.assertFalse(pr_ready.launcher_rust("launcher/README.md"))
|
|
self.assertFalse(pr_ready.launcher_rust("crates/horizon-client/src/game.rs"))
|
|
|
|
def test_scope_warns_on_size_and_mixed_areas(self):
|
|
self.assertEqual(pr_ready.scope_warnings({"crates/horizon-client/src/game.rs", "tools/test_food_parity.py",
|
|
"mods/horizon/src/test/java/dev/horizonlab/parity/X.java",
|
|
"docs/native-client.md"}, 120), [])
|
|
self.assertEqual(pr_ready.scope_warnings({"crates/horizon-theme/src/lib.rs", "launcher/src/theme.rs"}, 10), [])
|
|
warnings = pr_ready.scope_warnings({"crates/horizon-client/src/game.rs", "launcher/src/main.rs",
|
|
".github/workflows/ci.yml"}, 900)
|
|
self.assertEqual(len(warnings), 2)
|
|
self.assertIn("900 lines of code: over 500", warnings[0])
|
|
self.assertEqual(warnings[1], "mixes CI, game, launcher")
|
|
|
|
def test_documentation_and_tests_are_not_counted_in_the_size(self):
|
|
self.assertTrue(pr_ready.reviewed("tools/pr_ready.py"))
|
|
self.assertTrue(pr_ready.reviewed("crates/horizon-client/src/game.rs"))
|
|
self.assertFalse(pr_ready.reviewed("CONTRIBUTING.md"))
|
|
self.assertFalse(pr_ready.reviewed("tests/test_pr_ready.py"))
|
|
self.assertFalse(pr_ready.reviewed("tools/test_food_parity.py"))
|
|
self.assertFalse(pr_ready.reviewed("crates/horizon-core/tests/jmath.rs"))
|
|
|
|
def test_cargo_lock_changes_only_with_a_manifest(self):
|
|
self.assertIsNone(pr_ready.lock_problem({"crates/horizon-core/src/lib.rs"}))
|
|
self.assertIsNone(pr_ready.lock_problem({"Cargo.lock", "crates/horizon-client/Cargo.toml"}))
|
|
self.assertIsNone(pr_ready.lock_problem({"launcher/Cargo.lock", "crates/horizon-theme/Cargo.toml"}))
|
|
self.assertIn("Cargo.lock", pr_ready.lock_problem({"Cargo.lock", "crates/horizon-core/src/lib.rs"}))
|
|
|
|
def test_label_follows_what_players_get(self):
|
|
self.assertEqual(pr_ready.suggested_label({"tools/pr_ready.py", "CONTRIBUTING.md"}), "`skip-release`")
|
|
self.assertEqual(pr_ready.suggested_label({"crates/horizon-world/examples/probe.rs"}), "`skip-release`")
|
|
self.assertIn("feature", pr_ready.suggested_label({"crates/horizon-client/src/game.rs"}))
|
|
self.assertIn("feature", pr_ready.suggested_label({"launcher/src/main.rs"}))
|
|
|
|
def test_affected_harnesses_come_from_the_parity_runner(self):
|
|
found = {"food": "tools/test_food_parity.py", "datafix#1": "tools/test_datafix_parity.py",
|
|
"datafix#2": "tools/test_datafix_parity.py"}
|
|
with mock.patch.object(pr_ready.parity, "harnesses", return_value=found), \
|
|
mock.patch.object(pr_ready.parity, "load_state", return_value={"harnesses": {
|
|
"food": {"dependencies": ["crates/horizon-world/src/food.rs"]},
|
|
"datafix#1": {"dependencies": ["crates/horizon-world/src/datafix.rs"]},
|
|
"datafix#2": {"dependencies": ["crates/horizon-world/src/datafix.rs"]}}}), \
|
|
mock.patch.object(pr_ready.parity, "ROOT", pr_ready.parity.Path(".")):
|
|
self.assertEqual(pr_ready.affected_here({"crates/horizon-world/src/datafix.rs"}), ["datafix"])
|
|
self.assertEqual(pr_ready.affected_here({"README.md"}), [])
|
|
self.assertEqual(pr_ready.affected_here({"Cargo.lock"}), ["food", "datafix"])
|
|
|
|
def test_harnesses_are_listed_when_they_cannot_run(self):
|
|
changed = {"crates/horizon-world/src/food.rs"}
|
|
with mock.patch.object(pr_ready, "affected_here", return_value=["food", "light"]):
|
|
steps = pr_ready.plan(args(quick=True), "origin/main", changed, {"horizon-world": "crates/horizon-world"})
|
|
harness = steps[-1]
|
|
self.assertEqual(harness.status, "list")
|
|
self.assertIn("2 affected, to run in CI (--quick): food, light", harness.detail)
|
|
by_name = {step.name: step for step in steps}
|
|
self.assertIn("tests: horizon-world", by_name)
|
|
self.assertEqual(by_name["launcher clippy"].status, "skip")
|
|
self.assertIsNone(by_name["clippy"].status)
|
|
with mock.patch.object(pr_ready, "affected_here", return_value=["food"]):
|
|
steps = pr_ready.plan(args(host="macmini"), "origin/main", changed, {"horizon-world": "crates/horizon-world"})
|
|
self.assertIn("--changed-files", steps[-1].command)
|
|
self.assertIn("crates/horizon-world/src/food.rs", steps[-1].command)
|
|
|
|
def test_summaries_of_check_output(self):
|
|
output = "test result: ok. 3 passed; 0 failed;\ntest result: FAILED. 4 passed; 1 failed;\n"
|
|
self.assertEqual(pr_ready.tests_passed(output), "7 passed, 1 failed")
|
|
self.assertEqual(pr_ready.unittest_passed("....\nRan 12 tests in 1.0s\n\nOK\n"), "12 passed")
|
|
self.assertEqual(pr_ready.unittest_passed("Ran 12 tests in 1.0s\n\nFAILED (failures=2)\n"),
|
|
"12 run, FAILED (failures=2)")
|
|
parity_output = ("3 files changed: 2 harnesses depend on them, 2 without a pass for their current inputs.\n"
|
|
"\n1 passed, 1 failed in 2.0 min.\n light: /x/.local/parity-runner/logs/light.log\n")
|
|
self.assertEqual(pr_ready.parity_summary(parity_output), "2 affected, 1 passed, failed: light")
|
|
self.assertEqual(pr_ready.parity_summary("2 files changed: 0 harnesses depend on them, 0 without a pass for "
|
|
"their current inputs.\nNothing to run: every harness passed."),
|
|
"no harness affected")
|
|
self.assertEqual(pr_ready.duration(125), "2 min 5 s")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|