874b6f20c2
Основа — Horizon (MIT). История апстрима не переносится: этот репозиторий ведёт свою.
188 lines
11 KiB
Python
188 lines
11 KiB
Python
import json
|
|
from pathlib import Path
|
|
import tempfile
|
|
import unittest
|
|
from unittest import mock
|
|
|
|
from tools import port_report as report
|
|
from tools import version_port as port
|
|
|
|
|
|
def mapping():
|
|
return {
|
|
"classes": {
|
|
"net/minecraft/world/food/FoodData": {
|
|
"rust": {"crates/w/src/food.rs": ["tick"], "crates/c/src/hud.rs": []}, "harnesses": ["food"]},
|
|
"net/minecraft/world/entity/Entity": {
|
|
"rust": {f"crates/c/src/f{i}.rs": [] for i in range(5)}, "harnesses": ["food", "move"]},
|
|
"net/minecraft/client/Sky": {"rust": {"crates/c/src/sky.rs": []}, "harnesses": []},
|
|
"net/minecraft/world/entity/NewMob": {"rust": {"crates/c/src/new_mob.rs": []}, "harnesses": []},
|
|
"net/minecraft/util/datafix/DataFixers": {"rust": {"crates/w/src/datafix/mod.rs": []},
|
|
"harnesses": ["datafix"]},
|
|
},
|
|
"harnesses": {
|
|
"food": {"rust": ["crates/w/src/food.rs"], "classes": ["net/minecraft/world/food/FoodData"]},
|
|
"move": {"rust": [], "classes": ["net/minecraft/world/entity/Entity"]},
|
|
"datafix": {"rust": [], "classes": []},
|
|
},
|
|
}
|
|
|
|
|
|
def diff():
|
|
def methods(**kinds):
|
|
return {"methods": {k: kinds.get(k, []) for k in ("added", "removed", "changed")}}
|
|
changed = {
|
|
"net/minecraft/world/food/FoodData": methods(changed=["tick()V", "eat(I)V"]),
|
|
"net/minecraft/world/entity/Entity": methods(changed=["move()V"]),
|
|
"net/minecraft/client/Sky": methods(added=["stars()V"]),
|
|
"net/minecraft/util/datafix/DataFixers": methods(changed=["addFixers()V"]),
|
|
"net/minecraft/server/Unported": methods(changed=["run()V"]),
|
|
}
|
|
m = mapping()
|
|
return {
|
|
"facts": {"protocol_version": [776, 777], "world_version": [4903, 5023]},
|
|
"packets": {"play/serverbound": {"added": {"minecraft:punch": 46}, "removed": {"minecraft:swing": 63},
|
|
"renumbered": {}, "count": [69, 69]}},
|
|
"registries": {"added": {}, "removed": [], "changed": {
|
|
"minecraft:entity_type": {"added": ["minecraft:new_mob"]},
|
|
"minecraft:data_component_type": {"added": ["minecraft:attack_animation"],
|
|
"removed": ["minecraft:swing_animation"]}}},
|
|
"blocks": {"added": [], "removed": [], "changed": {}, "format": ["definition"]},
|
|
"items": {"added": [], "removed": [], "changed": {}, "components": {}},
|
|
"commands": {"added": [], "removed": [], "changed": {}},
|
|
"classes": {
|
|
"added": ["net/minecraft/world/entity/NewMob", "net/minecraft/util/datafix/schemas/V4996",
|
|
"net/minecraft/util/datafix/fixes/SwingSplitFix", "net/minecraft/network/protocol/game/Punch",
|
|
"net/minecraft/elsewhere/Thing"],
|
|
"removed": ["net/minecraft/network/protocol/game/Swing"], "moved": {},
|
|
"changed": changed,
|
|
"horizon": {name: m["classes"][name] for name in changed if name in m["classes"]},
|
|
},
|
|
}
|
|
|
|
|
|
class PortReportTests(unittest.TestCase):
|
|
def build(self, results=None, regen=None, harnesses=None):
|
|
with mock.patch.object(report, "version_sites", return_value=["crates/p/src/lib.rs:20"]), \
|
|
mock.patch.object(report, "files_naming", return_value=["crates/p/src/play.rs"]), \
|
|
mock.patch.object(report, "files_containing", return_value=["crates/w/src/blocks.rs"]):
|
|
return report.build(diff(), mapping(), "26.2", "26.3", {"id": "26.3"}, results, regen=regen,
|
|
harnesses=harnesses)
|
|
|
|
def test_harness_results_count_only_new_failures(self):
|
|
ran = {"prepare": {"status": "failed", "error": "javac exited 1",
|
|
"compile_errors": ["VanillaBlockExporter.java:9: cannot find symbol: class Foo"]},
|
|
"summary": {"pass": 1, "oracle": 1, "fail": 2, "new": 2, "known": 1},
|
|
"harnesses": {"food": {"status": "oracle", "baseline": "pass", "new": True, "compile_errors": ["X"]},
|
|
"move": {"status": "fail", "baseline": "fail", "new": False},
|
|
"light": {"status": "fail", "baseline": "pass", "new": True},
|
|
"sky": {"status": "pass", "baseline": "pass", "new": False}}}
|
|
built = self.build(harnesses=ran)
|
|
[prepare] = [i for i in built["items"] if i["kind"] == "prepare-failed"]
|
|
self.assertEqual(prepare["rank"], 1)
|
|
failed = {h: i for i in built["items"] for h in i.get("failed", [])}
|
|
self.assertEqual(failed["food"]["oracle"], ["food"])
|
|
self.assertNotIn("move", failed) # It fails on the old version too.
|
|
self.assertEqual(failed["light"]["title"], "Harness light: fails on 26.3")
|
|
text = report.markdown(built)
|
|
self.assertIn("1 pass, 1 whose oracle no longer compiles, 2 fail; 2 newly failing, 1 failing on 26.2 too", text)
|
|
self.assertIn("- javac: `VanillaBlockExporter.java:9: cannot find symbol: class Foo`", text)
|
|
|
|
def test_regen_results_replace_the_stale_tables(self):
|
|
regen = {"new": "26.3", "candidate": "/ports/26.3", "commit": "0123456789abcdef", "generators": {
|
|
"packet_ids": {"status": "regenerated", "outputs": ["crates/horizon-protocol/src/ids.rs"],
|
|
"stat": {"crates/horizon-protocol/src/ids.rs": [142, 134]}},
|
|
"entity_tracking": {"status": "unchanged", "outputs": ["crates/horizon-client/src/lan/tracking.rs"],
|
|
"stat": {}},
|
|
"lan_command_order": {"status": "failed", "outputs": ["crates/horizon-client/src/lan/command_order.rs"],
|
|
"error": "javac exited 1",
|
|
"compile_errors": ["ParityData.java:45: cannot find symbol: variable WORLD"]}}}
|
|
built = self.build(regen=regen)
|
|
self.assertFalse([i for i in built["items"] if i["kind"] == "regenerate"])
|
|
[failed] = [i for i in built["items"] if i["kind"] == "regen-failed"]
|
|
self.assertEqual(failed["rank"], 1)
|
|
self.assertIn("no longer compiles", failed["title"])
|
|
self.assertEqual(built["summary"]["generators"],
|
|
{"packet_ids": "regenerated", "entity_tracking": "unchanged", "lan_command_order": "failed"})
|
|
text = report.markdown(built)
|
|
self.assertIn("regenerated 1, unchanged 1, failed 1", text)
|
|
self.assertIn("`crates/horizon-protocol/src/ids.rs` (+142 -134)", text)
|
|
self.assertIn("- javac: `ParityData.java:45: cannot find symbol: variable WORLD`", text)
|
|
|
|
def test_ranks_and_kinds(self):
|
|
built = self.build()
|
|
kinds = [(i["rank"], i["kind"]) for i in built["items"]]
|
|
self.assertEqual(sorted(k for r, k in kinds if r == 1),
|
|
["components", "facts", "protocol", "regenerate", "regenerate", "regenerate",
|
|
"report-format", "save-format"])
|
|
self.assertEqual([i["id"] for i in built["items"]], list(range(1, len(kinds) + 1)))
|
|
self.assertEqual(kinds, sorted(kinds, key=lambda k: k[0]))
|
|
regenerated = {i["files"][0] for i in built["items"] if i["kind"] == "regenerate"}
|
|
self.assertEqual(regenerated, {"crates/horizon-core/src/version.rs", "crates/horizon-protocol/src/ids.rs",
|
|
"crates/horizon-client/src/lan/tracking.rs"})
|
|
|
|
def test_save_format_lists_new_schemas_and_fixes(self):
|
|
save = next(i for i in self.build()["items"] if i["kind"] == "save-format")
|
|
self.assertEqual(save["details"]["schemas"], ["net/minecraft/util/datafix/schemas/V4996"])
|
|
self.assertEqual(save["details"]["fixes"], ["net/minecraft/util/datafix/fixes/SwingSplitFix"])
|
|
self.assertEqual(save["files"], ["crates/w/src/datafix/mod.rs"])
|
|
|
|
def test_code_items_by_file(self):
|
|
items = {i["module"]: i for i in self.build()["items"] if "module" in i}
|
|
food = items["crates/w/src/food.rs"]
|
|
self.assertEqual((food["rank"], food["kind"], food["harnesses"]), (2, "rerun", ["food"]))
|
|
self.assertEqual(food["classes"]["net/minecraft/world/food/FoodData"]["named"], ["tick"])
|
|
sky = items["crates/c/src/sky.rs"]
|
|
self.assertEqual((sky["rank"], sky["kind"]), (3, "unproved"))
|
|
# Named by many files and none names the changed method: grouped by package, harnesses by coverage.
|
|
widely = items["widely named: world/entity"]
|
|
self.assertEqual(widely["harnesses"], ["food", "move"])
|
|
unported = next(i for i in self.build()["items"] if i["kind"] == "unported")
|
|
self.assertEqual(unported["packages"], {"net/minecraft/server": 1})
|
|
|
|
def test_failed_harness_ranks_first_in_rank_two(self):
|
|
items = self.build({"food": "fail", "move": "pass"})["items"]
|
|
first = next(i for i in items if i["rank"] == 2)
|
|
self.assertEqual((first["kind"], first["failed"]), ("harness-failed", ["food"]))
|
|
|
|
def test_new_content(self):
|
|
items = self.build()["items"]
|
|
self.assertIn("minecraft:new_mob", next(i for i in items if i["kind"] == "content")["entries"])
|
|
new = next(i for i in items if i["kind"] == "new-classes")
|
|
self.assertEqual((new["classes"], new["ported"]),
|
|
(["net/minecraft/world/entity/NewMob"], ["net/minecraft/world/entity/NewMob"]))
|
|
self.assertEqual(next(i for i in items if i["kind"] == "new-unported")["packages"],
|
|
{"net/minecraft/elsewhere": 1})
|
|
|
|
def test_markdown(self):
|
|
text = report.markdown(self.build())
|
|
self.assertTrue(text.startswith("# Port report: Minecraft 26.2 -> 26.3"))
|
|
self.assertIn("## 1. Blocks the switch", text)
|
|
self.assertIn("play/serverbound: added minecraft:punch; removed minecraft:swing", text)
|
|
self.assertIn("FoodData (2 methods; tick)", text)
|
|
|
|
def test_helpers(self):
|
|
self.assertEqual(report.snake("LivingEntity"), "living_entity")
|
|
entry = {"rust": {"a/x.rs": ["tick"], "a/living_entity.rs": [], "a/y.rs": ["tick", "eat"]}}
|
|
self.assertEqual(report.primary_file(entry, {"tick", "eat"}, "n/LivingEntity"), "a/y.rs")
|
|
self.assertEqual(report.primary_file(entry, {"other"}, "n/LivingEntity"), "a/living_entity.rs")
|
|
self.assertEqual(report.primary_file(entry, set(), "n/Other"), "a/living_entity.rs")
|
|
self.assertIsNone(report.primary_file({"rust": {f"{i}.rs": [] for i in range(4)}}, set(), "n/Other"))
|
|
|
|
|
|
class VersionPortReportTests(unittest.TestCase):
|
|
def test_harness_results_merge_shards(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
state = Path(directory) / "state.json"
|
|
state.write_text(json.dumps({"harnesses": {"food": {"status": "pass"}, "datafix#1": {"status": "pass"},
|
|
"datafix#2": {"status": "fail"}}}))
|
|
self.assertEqual(port.harness_results(state), {"food": "pass", "datafix": "fail"})
|
|
|
|
def test_class_names_drop_moved_old_names(self):
|
|
self.assertEqual(port.class_names({"a/X": 0, "a/Y": 0}, {"b/X": 0, "a/Y": 0}, {"a/X": "b/X"}),
|
|
["a/Y", "b/X"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|