mirror of
https://github.com/horizon-rust/horizon-mods.git
synced 2026-11-07 11:05:58 -10:00
* native health: also cargo check, and mark the mods whose own code doesn't compile The daily check only applied the mods, so versions that apply but don't compile on the newest Nightly (eatinganimation's ItemRenderState, hitbox_plus's module path, carryblock's removed Dim::min_y...) weren't marked broken_on, while every player's build leaves them out. After the apply pass, the mods that apply are checked together (cargo check, with Horizon's `mods` feature) in rounds: each round's errors are traced to the mod whose helper or support module holds them (scripts/native_trace.py, the launcher's rule), those mods and the ones needing them leave, and the rest compile again. An error of the kind two mods cause together (a name defined twice...) is checked again with that mod alone, so a pair is never marked. Mods that use an API the launcher prepares ([api], trait_method) aren't compiled here. Dependencies and horizon-mixins come from the Actions cache. Co-Authored-By: Claude Opus 5.5 <[email protected]> * native health: trace helpers whose signature holds an array type, mask each file once Co-Authored-By: Claude Opus 5.5 <[email protected]> * native health: an error in the call horizon-mixins generates for a helper is the helper's mod's Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: Horizon agent <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
111 lines
4.9 KiB
Python
111 lines
4.9 KiB
Python
"""python3 -m unittest discover -s scripts -p 'test_*.py'"""
|
|
import json
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from native_trace import Tracer, errors, item_ranges, masked
|
|
|
|
PRISTINE = """struct MeshTables { base: u8, layers: u8 }
|
|
pub fn resolve(x: u8) -> u8 {
|
|
x
|
|
}
|
|
"""
|
|
# One mod's helper inside a target (formatted with a trailing comma, an array type's `;` in its signature), another's
|
|
# support module; braces in strings, chars and comments must not end them early.
|
|
STAGED = """struct MeshTables { base: u8, layers: u8 }
|
|
pub fn resolve(x: u8) -> u8 {
|
|
if let Some(v) = {
|
|
fn fast_mesh_resolve(x: u8, _at: [f64; 3],) -> Option<u8> {
|
|
let _ = "}"; let _ = '}'; // }
|
|
let _ = r#"}"#;
|
|
Some(MeshTables { base: x }.base)
|
|
}
|
|
fast_mesh_resolve(x, [0.0; 3])
|
|
} {
|
|
return v;
|
|
}
|
|
x
|
|
}
|
|
mod carry_tables {
|
|
pub fn lift<'a>(t: &'a super::MeshTables) -> u8 { t.missing }
|
|
}
|
|
fn untouched() -> u8 { MeshTables { base: 1 }.base }
|
|
"""
|
|
|
|
|
|
def span(text, needle, file="src/main.rs"):
|
|
start = STAGED.encode().index(needle.encode())
|
|
return {"file_name": file, "byte_start": start, "byte_end": start + len(needle), "is_primary": True}
|
|
|
|
|
|
class Trace(unittest.TestCase):
|
|
def setUp(self):
|
|
self.dir = Path(tempfile.mkdtemp())
|
|
(self.dir / "stage/src").mkdir(parents=True)
|
|
(self.dir / "pristine/src").mkdir(parents=True)
|
|
(self.dir / "stage/src/main.rs").write_text(STAGED)
|
|
(self.dir / "pristine/src/main.rs").write_text(PRISTINE)
|
|
self.plan([
|
|
{"mod_id": "fast_mesh", "mod_version": "0.1.0", "file": "src/main.rs",
|
|
"helper_source": "fn fast_mesh_resolve(x: u8) -> Option<u8> { Some(MeshTables { base: x }.base) }"},
|
|
], [{"mod_id": "carryblock", "mod_version": "0.1.2", "file": "src/main.rs", "name": "carry_tables"}])
|
|
self.mods = {("fast_mesh", "0.1.0"), ("carryblock", "0.1.2")}
|
|
|
|
def plan(self, injections, modules):
|
|
(self.dir / "stage/.horizon-mixins-stage.json").write_text(
|
|
json.dumps({"schema": 6, "injections": injections, "modules": modules}))
|
|
|
|
def tracer(self, mods=None):
|
|
return Tracer(self.dir / "stage", self.dir / "pristine", mods or self.mods)
|
|
|
|
def test_an_error_in_a_helper_names_its_mod_though_formatting_changed_it(self):
|
|
self.assertEqual(self.tracer().owner(span(STAGED, "MeshTables { base: x }")), "fast_mesh")
|
|
|
|
def test_an_error_in_a_support_module_names_its_mod(self):
|
|
self.assertEqual(self.tracer().owner(span(STAGED, "t.missing")), "carryblock")
|
|
|
|
def test_an_error_in_the_generated_call_to_a_helper_names_its_mod(self):
|
|
self.assertEqual(self.tracer().owner(span(STAGED, "[0.0; 3]")), "fast_mesh")
|
|
|
|
def test_an_error_in_horizons_own_code_names_no_mod(self):
|
|
self.assertIsNone(self.tracer().owner(span(STAGED, "MeshTables { base: 1 }")))
|
|
self.assertIsNone(self.tracer().owner(span(STAGED, "return v")))
|
|
|
|
def test_a_path_through_another_crate_folder_resolves(self):
|
|
(self.dir / "stage/lib").mkdir()
|
|
s = span(STAGED, "t.missing", file="lib/../src/main.rs")
|
|
self.assertEqual(self.tracer().owner(s), "carryblock")
|
|
|
|
def test_a_function_the_pristine_source_has_is_not_a_mods(self):
|
|
(self.dir / "pristine/src/main.rs").write_text(PRISTINE + "fn fast_mesh_resolve() {}\n")
|
|
self.assertIsNone(self.tracer().owner(span(STAGED, "MeshTables { base: x }")))
|
|
|
|
def test_a_mod_not_built_or_two_owners_name_no_mod(self):
|
|
self.assertIsNone(self.tracer({("carryblock", "0.1.2")}).owner(span(STAGED, "MeshTables { base: x }")))
|
|
self.plan([{"mod_id": m, "mod_version": "1", "file": "src/main.rs",
|
|
"helper_source": "fn fast_mesh_resolve() {}"} for m in ("a", "b")], [])
|
|
self.assertIsNone(self.tracer({("a", "1"), ("b", "1")}).owner(span(STAGED, "MeshTables { base: x }")))
|
|
|
|
def test_braces_in_literals_and_comments_are_not_code(self):
|
|
code = masked(STAGED.encode())
|
|
(start, end), = item_ranges(code, "fn", "fast_mesh_resolve")
|
|
self.assertTrue(STAGED.encode()[start:end].endswith(b"}.base)\n }\n fast_mesh_resolve(x, [0.0; 3])\n }"))
|
|
self.assertEqual(len(code), len(STAGED.encode()))
|
|
|
|
def test_cargo_json_errors(self):
|
|
out = "\n".join([
|
|
"not json",
|
|
json.dumps({"reason": "compiler-artifact"}),
|
|
json.dumps({"reason": "compiler-message", "message": {"level": "warning", "message": "w", "spans": []}}),
|
|
json.dumps({"reason": "compiler-message", "message": {
|
|
"level": "error", "message": "missing fields", "code": {"code": "E0063"}, "rendered": "error[E0063]",
|
|
"spans": [{"file_name": "a.rs", "is_primary": True}, {"file_name": "b.rs", "is_primary": False}]}}),
|
|
])
|
|
(only,) = errors(out)
|
|
self.assertEqual((only["code"], [s["file_name"] for s in only["spans"]]), ("E0063", ["a.rs"]))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|